keyboard_layout_test.cpp 20.5 KB
Newer Older
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
1
2
3
/*
    KWin - the KDE window manager
    This file is part of the KDE project.
4

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
5
    SPDX-FileCopyrightText: 2017 Martin Gräßlin <mgraesslin@kde.org>
6

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
7
8
    SPDX-License-Identifier: GPL-2.0-or-later
*/
9
#include "kwin_wayland_test.h"
10
#include "abstract_client.h"
11
#include "keyboard_input.h"
12
#include "keyboard_layout.h"
13
#include "platform.h"
14
#include "virtualdesktops.h"
15
#include "wayland_server.h"
16
#include "workspace.h"
17
18

#include <KConfigGroup>
19
#include <KGlobalAccel>
20

21
22
#include <KWayland/Client/surface.h>

23
#include <QAction>
24
#include <QDBusConnection>
25
#include <QDBusConnectionInterface>
26
#include <QDBusMessage>
27
#include <QDBusPendingCall>
28

29
30
#include <linux/input.h>

31
32
33
34
35
36
37
38
using namespace KWin;
using namespace KWayland::Client;

static const QString s_socketName = QStringLiteral("wayland_test_kwin_keyboard_laout-0");

class KeyboardLayoutTest : public QObject
{
    Q_OBJECT
Andrey Butirsky's avatar
Andrey Butirsky committed
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public:
    KeyboardLayoutTest()
        : layoutsReconfiguredSpy(this, &KeyboardLayoutTest::layoutListChanged)
        , layoutChangedSpy(this, &KeyboardLayoutTest::layoutChanged)
    {
        QVERIFY(layoutsReconfiguredSpy.isValid());
        QVERIFY(layoutChangedSpy.isValid());

        QVERIFY(QDBusConnection::sessionBus().connect(QStringLiteral("org.kde.keyboard"), QStringLiteral("/Layouts"), QStringLiteral("org.kde.KeyboardLayouts"), QStringLiteral("layoutListChanged"), this, SIGNAL(layoutListChanged())));
        QVERIFY(QDBusConnection::sessionBus().connect(QStringLiteral("org.kde.keyboard"), QStringLiteral("/Layouts"), QStringLiteral("org.kde.KeyboardLayouts"), QStringLiteral("currentLayoutChanged"), this, SIGNAL(layoutChanged(QString))));
    }

Q_SIGNALS:
    void layoutChanged(const QString &name);
    void layoutListChanged();

55
56
57
58
59
60
private Q_SLOTS:
    void initTestCase();
    void init();
    void cleanup();

    void testReconfigure();
61
    void testChangeLayoutThroughDBus();
62
    void testPerLayoutShortcut();
63
    void testDBusServiceExport();
64
    void testVirtualDesktopPolicy();
65
    void testWindowPolicy();
66
    void testApplicationPolicy();
67
    void testNumLock();
68
69
70

private:
    void reconfigureLayouts();
Andrey Butirsky's avatar
Andrey Butirsky committed
71
72
73
74
75
76
    void resetLayouts();
    auto changeLayout(const QString &layoutName);
    void callSession(const QString &method);
    QSignalSpy layoutsReconfiguredSpy;
    QSignalSpy layoutChangedSpy;
    KConfigGroup layoutGroup;
77
78
};

79
80
81
82
void KeyboardLayoutTest::reconfigureLayouts()
{
    // create DBus signal to reload
    QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/Layouts"), QStringLiteral("org.kde.keyboard"), QStringLiteral("reloadConfig"));
Andrey Butirsky's avatar
Andrey Butirsky committed
83
84
    QVERIFY(QDBusConnection::sessionBus().send(message));

85
86
87
    QVERIFY(layoutsReconfiguredSpy.wait(1000));
    QCOMPARE(layoutsReconfiguredSpy.count(), 1);
    layoutsReconfiguredSpy.clear();
Andrey Butirsky's avatar
Andrey Butirsky committed
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
}

void KeyboardLayoutTest::resetLayouts()
{
    /* Switch Policy to destroy layouts from memory.
     * On return to original Policy they should reload from disk.
     */
    callSession(QStringLiteral("aboutToSaveSession"));

    const QString policy = layoutGroup.readEntry("SwitchMode", "Global");

    if (policy == QLatin1String("Global")) {
        layoutGroup.writeEntry("SwitchMode", "Desktop");
    } else {
        layoutGroup.deleteEntry("SwitchMode");
    }
    reconfigureLayouts();

    layoutGroup.writeEntry("SwitchMode", policy);
    reconfigureLayouts();

    callSession(QStringLiteral("loadSession"));
}

auto KeyboardLayoutTest::changeLayout(const QString &layoutName) {
    QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.keyboard"), QStringLiteral("/Layouts"), QStringLiteral("org.kde.KeyboardLayouts"), QStringLiteral("setLayout"));
    msg << layoutName;
    return QDBusConnection::sessionBus().asyncCall(msg);
}

void KeyboardLayoutTest::callSession(const QString &method) {
    QDBusMessage msg = QDBusMessage::createMethodCall(
                QStringLiteral("org.kde.KWin"),
                QStringLiteral("/Session"),
                QStringLiteral("org.kde.KWin.Session"),
                method);
    msg << QLatin1String();	// session name
    QVERIFY(QDBusConnection::sessionBus().call(msg).type() != QDBusMessage::ErrorMessage);
126
127
}

128
129
void KeyboardLayoutTest::initTestCase()
{
130
    qRegisterMetaType<KWin::AbstractClient*>();
131
132
    QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
    QVERIFY(applicationStartedSpy.isValid());
133
134
135
136
137
138
    kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024));
    QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit()));

    kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig));
    kwinApp()->setKxkbConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig));

Andrey Butirsky's avatar
Andrey Butirsky committed
139
140
141
    layoutGroup = kwinApp()->kxkbConfig()->group("Layout");
    layoutGroup.deleteGroup();

142
    kwinApp()->start();
143
    QVERIFY(applicationStartedSpy.wait());
144
    waylandServer()->initWorkspace();
Andrey Butirsky's avatar
Andrey Butirsky committed
145
146
147
148
149

    // don't get DBus signal on one-layout configuration
//    QVERIFY(layoutsReconfiguredSpy.wait());
//    QCOMPARE(layoutsReconfiguredSpy.count(), 1);
//    layoutsReconfiguredSpy.clear();
150
151
152
153
}

void KeyboardLayoutTest::init()
{
154
    QVERIFY(Test::setupWaylandConnection());
155
156
157
158
}

void KeyboardLayoutTest::cleanup()
{
159
    Test::destroyWaylandConnection();
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
}

void KeyboardLayoutTest::testReconfigure()
{
    // verifies that we can change the keymap

    // default should be a keymap with only us layout
    auto xkb = input()->keyboard()->xkb();
    QCOMPARE(xkb->numberOfLayouts(), 1u);
    QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)"));
    auto layouts = xkb->layoutNames();
    QCOMPARE(layouts.size(), 1);
    QVERIFY(layouts.contains(0));
    QCOMPARE(layouts[0], QStringLiteral("English (US)"));

    // create a new keymap
    KConfigGroup layoutGroup = kwinApp()->kxkbConfig()->group("Layout");
    layoutGroup.writeEntry("LayoutList", QStringLiteral("de,us"));
    layoutGroup.sync();

180
    reconfigureLayouts();
181
    // now we should have two layouts
182
    QCOMPARE(xkb->numberOfLayouts(), 2u);
183
184
185
186
187
188
189
190
191
192
    // default layout is German
    QCOMPARE(xkb->layoutName(), QStringLiteral("German"));
    layouts = xkb->layoutNames();
    QCOMPARE(layouts.size(), 2);
    QVERIFY(layouts.contains(0));
    QVERIFY(layouts.contains(1));
    QCOMPARE(layouts[0], QStringLiteral("German"));
    QCOMPARE(layouts[1], QStringLiteral("English (US)"));
}

193
194
195
196
197
198
199
void KeyboardLayoutTest::testChangeLayoutThroughDBus()
{
    // this test verifies that the layout can be changed through DBus
    // first configure layouts
    layoutGroup.writeEntry("LayoutList", QStringLiteral("de,us,de(neo)"));
    layoutGroup.sync();
    reconfigureLayouts();
Andrey Butirsky's avatar
Andrey Butirsky committed
200
    // now we should have three layouts
201
    auto xkb = input()->keyboard()->xkb();
202
    QCOMPARE(xkb->numberOfLayouts(), 3u);
203
204
205
206
    // default layout is German
    xkb->switchToLayout(0);
    QCOMPARE(xkb->layoutName(), QStringLiteral("German"));

Andrey Butirsky's avatar
Andrey Butirsky committed
207
208
209
210
211
    // place garbage to layout entry
    layoutGroup.writeEntry("LayoutDefaultFoo", "garbage");
    // make sure the garbage is wiped out on saving
    resetLayouts();
    QVERIFY(!layoutGroup.hasKey("LayoutDefaultFoo"));
212

Andrey Butirsky's avatar
Andrey Butirsky committed
213
    // now change through DBus to English
214
215
216
217
    auto reply = changeLayout(QStringLiteral("English (US)"));
    reply.waitForFinished();
    QVERIFY(!reply.isError());
    QCOMPARE(reply.reply().arguments().first().toBool(), true);
218
219
220
    QVERIFY(layoutChangedSpy.wait());
    QCOMPARE(layoutChangedSpy.count(), 1);
    layoutChangedSpy.clear();
221

Andrey Butirsky's avatar
Andrey Butirsky committed
222
223
224
225
226
    // layout should persist after reset
    resetLayouts();

    QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)"));

227
228
229
230
231
    // switch to a layout which does not exist
    reply = changeLayout(QStringLiteral("French"));
    QVERIFY(!reply.isError());
    QCOMPARE(reply.reply().arguments().first().toBool(), false);
    QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)"));
Andrey Butirsky's avatar
Andrey Butirsky committed
232
    QVERIFY(!layoutChangedSpy.wait(1000));
233
    QVERIFY(layoutChangedSpy.isEmpty());
234
235
236
237
238
239

    // switch to another layout should work
    reply = changeLayout(QStringLiteral("German"));
    QVERIFY(!reply.isError());
    QCOMPARE(reply.reply().arguments().first().toBool(), true);
    QCOMPARE(xkb->layoutName(), QStringLiteral("German"));
Andrey Butirsky's avatar
Andrey Butirsky committed
240
241
242
    QVERIFY(layoutChangedSpy.wait(1000));
    QCOMPARE(layoutChangedSpy.count(), 1);
    layoutChangedSpy.clear();
243
244
245
246
247
248

    // switching to same layout should also work
    reply = changeLayout(QStringLiteral("German"));
    QVERIFY(!reply.isError());
    QCOMPARE(reply.reply().arguments().first().toBool(), true);
    QCOMPARE(xkb->layoutName(), QStringLiteral("German"));
Andrey Butirsky's avatar
Andrey Butirsky committed
249
    QVERIFY(!layoutChangedSpy.wait(1000));
250
    QVERIFY(layoutChangedSpy.isEmpty());
251
252
}

253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
void KeyboardLayoutTest::testPerLayoutShortcut()
{
    // this test verifies that per-layout global shortcuts are working correctly.
    // first configure layouts
    layoutGroup.writeEntry("LayoutList", QStringLiteral("us,de,de(neo)"));
    layoutGroup.sync();

    // and create the global shortcuts
    const QString componentName = QStringLiteral("KDE Keyboard Layout Switcher");
    QAction *a = new QAction(this);
    a->setObjectName(QStringLiteral("Switch keyboard layout to English (US)"));
    a->setProperty("componentName", componentName);
    KGlobalAccel::self()->setShortcut(a, QList<QKeySequence>{Qt::CTRL+Qt::ALT+Qt::Key_1}, KGlobalAccel::NoAutoloading);
    delete a;
    a = new QAction(this);
    a->setObjectName(QStringLiteral("Switch keyboard layout to German"));
    a->setProperty("componentName", componentName);
    KGlobalAccel::self()->setShortcut(a, QList<QKeySequence>{Qt::CTRL+Qt::ALT+Qt::Key_2}, KGlobalAccel::NoAutoloading);
    delete a;

    // now we should have three layouts
    auto xkb = input()->keyboard()->xkb();
Andrey Butirsky's avatar
Andrey Butirsky committed
275
276
    reconfigureLayouts();
    QCOMPARE(xkb->numberOfLayouts(), 3u);
277
278
    // default layout is English
    xkb->switchToLayout(0);
Andrey Butirsky's avatar
Andrey Butirsky committed
279
    QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)"));
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300

    // now switch to English through the global shortcut
    quint32 timestamp = 1;
    kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++);
    kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++);
    kwinApp()->platform()->keyboardKeyPressed(KEY_2, timestamp++);
    QVERIFY(layoutChangedSpy.wait());
    // now layout should be German
    QCOMPARE(xkb->layoutName(), QStringLiteral("German"));
    // release keys again
    kwinApp()->platform()->keyboardKeyReleased(KEY_2, timestamp++);
    // switch back to English
    kwinApp()->platform()->keyboardKeyPressed(KEY_1, timestamp++);
    QVERIFY(layoutChangedSpy.wait());
    QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)"));
    // release keys again
    kwinApp()->platform()->keyboardKeyReleased(KEY_1, timestamp++);
    kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++);
    kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++);
}

301
302
303
304
305
306
307
308
309
void KeyboardLayoutTest::testDBusServiceExport()
{
    // verifies that the dbus service is only exported if there are at least two layouts

    // first configure layouts, with just one layout
    layoutGroup.writeEntry("LayoutList", QStringLiteral("us"));
    layoutGroup.sync();
    reconfigureLayouts();
    auto xkb = input()->keyboard()->xkb();
310
    QCOMPARE(xkb->numberOfLayouts(), 1u);
311
    // default layout is English
Andrey Butirsky's avatar
Andrey Butirsky committed
312
    QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)"));
313
    // with one layout we should not have the dbus interface
314
    QVERIFY(!QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.kde.keyboard")).value());
315
316
317
318
319

    // reconfigure to two layouts
    layoutGroup.writeEntry("LayoutList", QStringLiteral("us,de"));
    layoutGroup.sync();
    reconfigureLayouts();
320
321
    QCOMPARE(xkb->numberOfLayouts(), 2u);
    QVERIFY(QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.kde.keyboard")).value());
322
323
324
325
326

    // and back to one layout
    layoutGroup.writeEntry("LayoutList", QStringLiteral("us"));
    layoutGroup.sync();
    reconfigureLayouts();
327
328
    QCOMPARE(xkb->numberOfLayouts(), 1u);
    QVERIFY(!QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.kde.keyboard")).value());
329
330
}

331
332
333
334
335
336
337
void KeyboardLayoutTest::testVirtualDesktopPolicy()
{
    layoutGroup.writeEntry("LayoutList", QStringLiteral("us,de,de(neo)"));
    layoutGroup.writeEntry("SwitchMode", QStringLiteral("Desktop"));
    layoutGroup.sync();
    reconfigureLayouts();
    auto xkb = input()->keyboard()->xkb();
338
    QCOMPARE(xkb->numberOfLayouts(), 3u);
Andrey Butirsky's avatar
Andrey Butirsky committed
339
    QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)"));
340
341
342
343
344
345

    VirtualDesktopManager::self()->setCount(4);
    QCOMPARE(VirtualDesktopManager::self()->count(), 4u);
    auto desktops = VirtualDesktopManager::self()->desktops();
    QCOMPARE(desktops.count(), 4);

Andrey Butirsky's avatar
Andrey Butirsky committed
346
347
348
349
350
351
352
    // give desktops different layouts
    uint desktop, layout;
    for (desktop = 0; desktop < VirtualDesktopManager::self()->count(); ++desktop) {
        // switch to another virtual desktop
        VirtualDesktopManager::self()->setCurrent(desktops.at(desktop));
        QCOMPARE(desktops.at(desktop), VirtualDesktopManager::self()->currentDesktop());
        // should be reset to English
353
        QCOMPARE(xkb->currentLayout(), 0);
Andrey Butirsky's avatar
Andrey Butirsky committed
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
        // change first desktop to German
        layout = (desktop + 1) % xkb->numberOfLayouts();
        changeLayout(xkb->layoutNames()[layout]).waitForFinished();
        QCOMPARE(xkb->currentLayout(), layout);
    }

    // imitate app restart to test layouts saving feature
    resetLayouts();

    // check layout set on desktop switching as intended
    for(--desktop;;) {
        QCOMPARE(desktops.at(desktop), VirtualDesktopManager::self()->currentDesktop());
        layout = (desktop + 1) % xkb->numberOfLayouts();
        QCOMPARE(xkb->currentLayout(), layout);
        if (--desktop >= VirtualDesktopManager::self()->count())	// overflow
            break;
        VirtualDesktopManager::self()->setCurrent(desktops.at(desktop));
    }
372
373

    // remove virtual desktops
Andrey Butirsky's avatar
Andrey Butirsky committed
374
375
    desktop = 0;
    const KWin::VirtualDesktop* deletedDesktop = desktops.last();
376
    VirtualDesktopManager::self()->setCount(1);
377
    QCOMPARE(xkb->currentLayout(), layout = (desktop + 1) % xkb->numberOfLayouts());
Andrey Butirsky's avatar
Andrey Butirsky committed
378
    QCOMPARE(xkb->layoutName(), QStringLiteral("German"));
379
380
381
382
383
384
385
386

    // add another desktop
    VirtualDesktopManager::self()->setCount(2);
    // switching to it should result in going to default
    desktops = VirtualDesktopManager::self()->desktops();
    QCOMPARE(desktops.count(), 2);
    QCOMPARE(desktops.first(), VirtualDesktopManager::self()->currentDesktop());
    VirtualDesktopManager::self()->setCurrent(desktops.last());
387
    QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)"));
388

Andrey Butirsky's avatar
Andrey Butirsky committed
389
390
391
392
393
394
    // check there are no more layouts left in config than the last actual non-default layouts number
    QSignalSpy deletedDesktopSpy(deletedDesktop, &VirtualDesktop::aboutToBeDestroyed);
    QVERIFY(deletedDesktopSpy.isValid());
    QVERIFY(deletedDesktopSpy.wait());
    resetLayouts();
    QCOMPARE(layoutGroup.keyList().filter( QStringLiteral("LayoutDefault") ).count(), 1);
395
396
}

397
398
399
400
401
402
403
void KeyboardLayoutTest::testWindowPolicy()
{
    layoutGroup.writeEntry("LayoutList", QStringLiteral("us,de,de(neo)"));
    layoutGroup.writeEntry("SwitchMode", QStringLiteral("Window"));
    layoutGroup.sync();
    reconfigureLayouts();
    auto xkb = input()->keyboard()->xkb();
404
    QCOMPARE(xkb->numberOfLayouts(), 3u);
Andrey Butirsky's avatar
Andrey Butirsky committed
405
    QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)"));
406
407
408
409

    // create a window
    using namespace KWayland::Client;
    QScopedPointer<Surface> surface(Test::createSurface());
410
    QScopedPointer<XdgShellSurface> shellSurface(Test::createXdgShellStableSurface(surface.data()));
411
412
413
414
415
416
    auto c1 = Test::renderAndWaitForShown(surface.data(), QSize(100, 100), Qt::blue);
    QVERIFY(c1);

    // now switch layout
    auto reply = changeLayout(QStringLiteral("German"));
    reply.waitForFinished();
Andrey Butirsky's avatar
Andrey Butirsky committed
417
    QCOMPARE(xkb->layoutName(), QStringLiteral("German"));
418
419
420

    // create a second window
    QScopedPointer<Surface> surface2(Test::createSurface());
421
    QScopedPointer<XdgShellSurface> shellSurface2(Test::createXdgShellStableSurface(surface2.data()));
422
423
424
    auto c2 = Test::renderAndWaitForShown(surface2.data(), QSize(100, 100), Qt::red);
    QVERIFY(c2);
    // this should have switched back to English
Andrey Butirsky's avatar
Andrey Butirsky committed
425
    QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)"));
426
427
428
    // now change to another layout
    reply = changeLayout(QStringLiteral("German (Neo 2)"));
    reply.waitForFinished();
Andrey Butirsky's avatar
Andrey Butirsky committed
429
    QCOMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)"));
430
431
432

    // activate other window
    workspace()->activateClient(c1);
433
    QCOMPARE(xkb->layoutName(), QStringLiteral("German"));
434
    workspace()->activateClient(c2);
435
    QCOMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)"));
436
437
}

438
439
440
441
442
443
444
void KeyboardLayoutTest::testApplicationPolicy()
{
    layoutGroup.writeEntry("LayoutList", QStringLiteral("us,de,de(neo)"));
    layoutGroup.writeEntry("SwitchMode", QStringLiteral("WinClass"));
    layoutGroup.sync();
    reconfigureLayouts();
    auto xkb = input()->keyboard()->xkb();
Andrey Butirsky's avatar
Andrey Butirsky committed
445
446
    QCOMPARE(xkb->numberOfLayouts(), 3u);
    QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)"));
447
448
449
450

    // create a window
    using namespace KWayland::Client;
    QScopedPointer<Surface> surface(Test::createSurface());
451
    QScopedPointer<XdgShellSurface> shellSurface(Test::createXdgShellStableSurface(surface.data()));
Andrey Butirsky's avatar
Andrey Butirsky committed
452
    shellSurface->setAppId(QByteArrayLiteral("org.kde.foo"));
453
454
455
456
457
    auto c1 = Test::renderAndWaitForShown(surface.data(), QSize(100, 100), Qt::blue);
    QVERIFY(c1);

    // create a second window
    QScopedPointer<Surface> surface2(Test::createSurface());
458
    QScopedPointer<XdgShellSurface> shellSurface2(Test::createXdgShellStableSurface(surface2.data()));
Andrey Butirsky's avatar
Andrey Butirsky committed
459
    shellSurface2->setAppId(QByteArrayLiteral("org.kde.foo"));
460
461
    auto c2 = Test::renderAndWaitForShown(surface2.data(), QSize(100, 100), Qt::red);
    QVERIFY(c2);
Andrey Butirsky's avatar
Andrey Butirsky committed
462
463
464
    // now switch layout
    layoutChangedSpy.clear();
    changeLayout(QStringLiteral("German (Neo 2)"));
465
    QVERIFY(layoutChangedSpy.wait());
Andrey Butirsky's avatar
Andrey Butirsky committed
466
467
468
469
470
471
472
473
474
    QCOMPARE(layoutChangedSpy.count(), 1);
    QCOMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)"));

    resetLayouts();
    // to trigger layout apply for current client
    workspace()->activateClient(c1);
    workspace()->activateClient(c2);

    QCOMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)"));
475
476
477

    // activate other window
    workspace()->activateClient(c1);
Andrey Butirsky's avatar
Andrey Butirsky committed
478
479
480
    // it is the same application and should not switch the layout
    QVERIFY(!layoutChangedSpy.wait(1000));
    QCOMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)"));
481
    workspace()->activateClient(c2);
Andrey Butirsky's avatar
Andrey Butirsky committed
482
483
    QVERIFY(!layoutChangedSpy.wait(1000));
    QCOMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)"));
484
485
486
487

    shellSurface2.reset();
    surface2.reset();
    QVERIFY(Test::waitForWindowDestroyed(c2));
Andrey Butirsky's avatar
Andrey Butirsky committed
488
489
490
491
492
    QVERIFY(!layoutChangedSpy.wait(1000));
    QCOMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)"));

    resetLayouts();
    QCOMPARE(layoutGroup.keyList().filter( QStringLiteral("LayoutDefault") ).count(), 1);
493
494
}

495
496
497
void KeyboardLayoutTest::testNumLock()
{
    qputenv("KWIN_FORCE_NUM_LOCK_EVALUATION", "1");
498
499
500
501
    layoutGroup.writeEntry("LayoutList", QStringLiteral("us"));
    layoutGroup.sync();
    reconfigureLayouts();

502
    auto xkb = input()->keyboard()->xkb();
503
    QCOMPARE(xkb->numberOfLayouts(), 1u);
Andrey Butirsky's avatar
Andrey Butirsky committed
504
    QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)"));
505

506
    // by default not set
507
    QVERIFY(!xkb->leds().testFlag(Xkb::LED::NumLock));
508
509
510
511
    quint32 timestamp = 0;
    kwinApp()->platform()->keyboardKeyPressed(KEY_NUMLOCK, timestamp++);
    kwinApp()->platform()->keyboardKeyReleased(KEY_NUMLOCK, timestamp++);
    // now it should be on
512
    QVERIFY(xkb->leds().testFlag(Xkb::LED::NumLock));
513
514
515
    // and back to off
    kwinApp()->platform()->keyboardKeyPressed(KEY_NUMLOCK, timestamp++);
    kwinApp()->platform()->keyboardKeyReleased(KEY_NUMLOCK, timestamp++);
516
    QVERIFY(!xkb->leds().testFlag(Xkb::LED::NumLock));
517
518

    // let's reconfigure to enable through config
519
    auto group = InputConfig::self()->inputConfig()->group("Keyboard");
520
521
522
523
    group.writeEntry("NumLock", 0);
    group.sync();
    xkb->reconfigure();
    // now it should be on
524
    QVERIFY(xkb->leds().testFlag(Xkb::LED::NumLock));
525
526
527
    // pressing should result in it being off
    kwinApp()->platform()->keyboardKeyPressed(KEY_NUMLOCK, timestamp++);
    kwinApp()->platform()->keyboardKeyReleased(KEY_NUMLOCK, timestamp++);
528
    QVERIFY(!xkb->leds().testFlag(Xkb::LED::NumLock));
529
530
531
532

    // pressing again should enable it
    kwinApp()->platform()->keyboardKeyPressed(KEY_NUMLOCK, timestamp++);
    kwinApp()->platform()->keyboardKeyReleased(KEY_NUMLOCK, timestamp++);
533
    QVERIFY(xkb->leds().testFlag(Xkb::LED::NumLock));
534
535
536
537
538

    // now reconfigure to disable on load
    group.writeEntry("NumLock", 1);
    group.sync();
    xkb->reconfigure();
539
    QVERIFY(!xkb->leds().testFlag(Xkb::LED::NumLock));
540
541
}

542
543
WAYLANDTEST_MAIN(KeyboardLayoutTest)
#include "keyboard_layout_test.moc"