on xenial help sign in
Keymap switching support Fixes: #1412492, #1491340, #1524400

Approved by: Unity8 CI Bot, Michael Terry

commitceced5854f63ba7c59380c1c7ffeb3710fc819ec authorLukáš Tinkl <lukas.tinkl@canonical.com> dateWed 23 Mar 2016, 09:50 (11 years ago) parentfc57df521777f9
20 files changed+263−9
plugins/AccountsService/AccountsService.cpp+26 plugins/AccountsService/AccountsService.h+5 qml/Shell.qml+50 qml/Stages/AbstractStage.qml+1 qml/Stages/ApplicationWindow.qml+4 qml/Stages/DesktopStage.qml+4 qml/Stages/PhoneStage.qml+2 qml/Stages/SpreadDelegate.qml+1 qml/Stages/SurfaceContainer.qml+13 qml/Stages/TabletStage.qml+2 tests/mocks/AccountsService/AccountsService.cpp+17 tests/mocks/AccountsService/AccountsService.h+8 tests/mocks/QMenuModel/QDBusActionGroup.qml+5−5 tests/mocks/Unity/Application/MirSurface.cpp+17−1 tests/mocks/Unity/Application/MirSurface.h+5 tests/plugins/AccountsService/PropertiesServer.cpp+9 tests/plugins/AccountsService/PropertiesServer.h−1 tests/plugins/AccountsService/client.cpp+30 tests/qmltests/Stages/tst_DesktopStage.qml−2 tests/qmltests/tst_Shell.qml+64
plugins/AccountsService/AccountsService.cpp+26
@@ -37,6 +37,7 @@
3737#define PROP_ENABLE_INDICATORS_WHILE_LOCKED QStringLiteral("EnableIndicatorsWhileLocked")
3838#define PROP_ENABLE_LAUNCHER_WHILE_LOCKED QStringLiteral("EnableLauncherWhileLocked")
3939#define PROP_FAILED_LOGINS QStringLiteral("FailedLogins")
40+#define PROP_INPUT_SOURCES QStringLiteral("InputSources")
4041#define PROP_LICENSE_ACCEPTED QStringLiteral("LicenseAccepted")
4142#define PROP_LICENSE_BASE_PATH QStringLiteral("LicenseBasePath")
4243#define PROP_MOUSE_CURSOR_SPEED QStringLiteral("MouseCursorSpeed")
@@ -55,6 +56,10 @@
5556#define PROP_TOUCHPAD_TAP_TO_CLICK QStringLiteral("TouchpadTapToClick")
5657#define PROP_TOUCHPAD_TWO_FINGER_SCROLL QStringLiteral("TouchpadTwoFingerScroll")
5758
59+using StringMap = QMap<QString,QString>;
60+using StringMapList = QList<StringMap>;
61+Q_DECLARE_METATYPE(StringMapList)
62+
5863
5964QVariant primaryButtonConverter(const QVariant &value)
6065{
@@ -83,6 +88,7 @@ AccountsService::AccountsService(QObject* parent, const QString &user)
8388 registerProperty(IFACE_ACCOUNTS_USER, PROP_BACKGROUND_FILE, QStringLiteral("backgroundFileChanged"));
8489 registerProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL, QStringLiteral("emailChanged"));
8590 registerProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME, QStringLiteral("realNameChanged"));
91+ registerProperty(IFACE_ACCOUNTS_USER, PROP_INPUT_SOURCES, QStringLiteral("keymapsChanged"));
8692 registerProperty(IFACE_LOCATION_HERE, PROP_LICENSE_ACCEPTED, QStringLiteral("hereEnabledChanged"));
8793 registerProperty(IFACE_LOCATION_HERE, PROP_LICENSE_BASE_PATH, QStringLiteral("hereLicensePathChanged"));
8894 registerProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_LAUNCHER_WHILE_LOCKED, QStringLiteral("enableLauncherWhileLockedChanged"));
@@ -246,6 +252,26 @@ void AccountsService::setEmail(const QString &email)
246252 setProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL, email);
247253}
248254
255+QStringList AccountsService::keymaps() const
256+{
257+ auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_INPUT_SOURCES);
258+ QDBusArgument arg = value.value<QDBusArgument>();
259+ StringMapList maps = qdbus_cast<StringMapList>(arg);
260+ QStringList simplifiedMaps;
261+
262+ Q_FOREACH(const StringMap &map, maps) {
263+ Q_FOREACH(const QString &entry, map) {
264+ simplifiedMaps.append(entry);
265+ }
266+ }
267+
268+ if (!simplifiedMaps.isEmpty()) {
269+ return simplifiedMaps;
270+ }
271+
272+ return {QStringLiteral("us")};
273+}
274+
249275uint AccountsService::failedLogins() const
250276{
251277 return getProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS).toUInt();
plugins/AccountsService/AccountsService.h+5
@@ -72,6 +72,9 @@ class AccountsService: public QObject
7272 NOTIFY hereLicensePathChanged)
7373 Q_PROPERTY(QString realName READ realName WRITE setRealName NOTIFY realNameChanged)
7474 Q_PROPERTY(QString email READ email WRITE setEmail NOTIFY emailChanged)
75+ Q_PROPERTY(QStringList keymaps
76+ READ keymaps
77+ NOTIFY keymapsChanged)
7578
7679public:
7780 enum PasswordDisplayHint {
@@ -103,6 +106,7 @@ public:
103106 void setRealName(const QString &realName);
104107 QString email() const;
105108 void setEmail(const QString &email);
109+ QStringList keymaps() const;
106110
107111Q_SIGNALS:
108112 void userChanged();
@@ -118,6 +122,7 @@ Q_SIGNALS:
118122 void hereLicensePathChanged();
119123 void realNameChanged();
120124 void emailChanged();
125+ void keymapsChanged();
121126
122127private Q_SLOTS:
123128 void onPropertiesChanged(const QString &user, const QString &interface, const QStringList &changed);
qml/Shell.qml+50
@@ -102,6 +102,8 @@ Item {
102102 // internal props from here onwards
103103 readonly property var mainApp:
104104 applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainApp : null
105+ readonly property var mainAppWindow:
106+ applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainAppWindow : null
105107
106108 // Disable everything while greeter is waiting, so that the user can't swipe
107109 // the greeter or launcher until we know whether the session is locked.
@@ -719,6 +721,54 @@ Item {
719721 onMouseMoved: { cursor.opacity = 1; }
720722 }
721723
724+ // keymap switching
725+ GlobalShortcut {
726+ shortcut: Qt.MetaModifier|Qt.Key_Space
727+ onTriggered: keymapPriv.nextKeymap()
728+ active: keymapPriv.keymapCount > 1
729+ }
730+
731+ GlobalShortcut {
732+ shortcut: Qt.MetaModifier|Qt.ShiftModifier|Qt.Key_Space
733+ onTriggered: keymapPriv.previousKeymap()
734+ active: keymapPriv.keymapCount > 1
735+ }
736+
737+ QtObject {
738+ id: keymapPriv
739+
740+ readonly property var keymaps: AccountsService.keymaps
741+ readonly property int keymapCount: keymaps.length
742+ property int currentKeymapIndex: 0 // the new one that we're setting
743+ onCurrentKeymapIndexChanged: switchToKeymap();
744+
745+ function nextKeymap() {
746+ var nextIndex = 0;
747+
748+ if (currentKeymapIndex !== -1 && currentKeymapIndex < keymapCount - 1) {
749+ nextIndex = currentKeymapIndex + 1;
750+ }
751+ currentKeymapIndex = nextIndex;
752+ }
753+
754+ function previousKeymap() {
755+ var prevIndex = keymapCount - 1;
756+
757+ if (currentKeymapIndex > 0) {
758+ prevIndex = currentKeymapIndex - 1;
759+ }
760+ currentKeymapIndex = prevIndex;
761+ }
762+
763+ function switchToKeymap() {
764+ if (mainAppWindow) {
765+ mainAppWindow.switchToKeymap(keymaps[currentKeymapIndex]);
766+ }
767+ }
768+ }
769+
770+ onMainAppWindowChanged: keymapPriv.switchToKeymap()
771+
722772 Rectangle {
723773 id: shutdownFadeOutRectangle
724774 z: cursor.z + 1
qml/Stages/AbstractStage.qml+1
@@ -45,6 +45,7 @@ Rectangle {
4545
4646 // To be read from outside
4747 property var mainApp: null
48+ property var mainAppWindow: null
4849 property int mainAppWindowOrientationAngle: 0
4950 property bool orientationChangesEnabled
5051 property int supportedOrientations: Qt.PortraitOrientation
qml/Stages/ApplicationWindow.qml+4
@@ -39,6 +39,10 @@ FocusScope {
3939 property int requestedWidth: -1
4040 property int requestedHeight: -1
4141
42+ function switchToKeymap(keymap) {
43+ sessionContainer.surfaceContainer.switchToKeymap(keymap);
44+ }
45+
4246 readonly property int minimumWidth: sessionContainer.surface ? sessionContainer.surface.minimumWidth : 0
4347 readonly property int minimumHeight: sessionContainer.surface ? sessionContainer.surface.minimumHeight : 0
4448 readonly property int maximumWidth: sessionContainer.surface ? sessionContainer.surface.maximumWidth : 0
qml/Stages/DesktopStage.qml+4
@@ -40,6 +40,8 @@ AbstractStage {
4040 ? ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
4141 : null
4242
43+ mainAppWindow: priv.focusedAppDelegate ? priv.focusedAppDelegate.appWindow : null
44+
4345 // application windows never rotate independently
4446 mainAppWindowOrientationAngle: shellOrientationAngle
4547
@@ -284,6 +286,8 @@ AbstractStage {
284286 property bool visuallyMaximized: false
285287 property bool visuallyMinimized: false
286288
289+ readonly property alias appWindow: decoratedWindow.window
290+
287291 onFocusChanged: {
288292 if (focus && ApplicationManager.focusedApplicationId !== appId) {
289293 ApplicationManager.focusApplication(appId);
qml/Stages/PhoneStage.qml+2
@@ -111,6 +111,8 @@ AbstractStage {
111111 ? applicationManager.findApplication(applicationManager.focusedApplicationId)
112112 : null
113113
114+ mainAppWindow: priv.focusedAppDelegate ? priv.focusedAppDelegate.appWindow : null
115+
114116 orientationChangesEnabled: priv.focusedAppOrientationChangesEnabled
115117 && !priv.focusedAppDelegateIsDislocated
116118 && !(priv.focusedAppDelegate && priv.focusedAppDelegate.xBehavior.running)
qml/Stages/SpreadDelegate.qml+1
@@ -37,6 +37,7 @@ FocusScope {
3737 | Qt.LandscapeOrientation
3838 | Qt.InvertedPortraitOrientation
3939 | Qt.InvertedLandscapeOrientation
40+ readonly property alias appWindow: appWindow
4041
4142 // to be set from outside
4243 property bool interactive: true
qml/Stages/SurfaceContainer.qml+13
@@ -19,6 +19,7 @@ import Ubuntu.Components 1.3
1919import Ubuntu.Gestures 0.1 // For TouchGate
2020import Utils 0.1 // for InputWatcher
2121import Unity.Application 0.1 // for MirSurfaceItem
22+import AccountsService 0.1
2223
2324FocusScope {
2425 id: root
@@ -34,10 +35,22 @@ FocusScope {
3435 property int requestedWidth: -1
3536 property int requestedHeight: -1
3637
38+ property string savedKeymap: AccountsService.keymaps[0] // start with the user default
39+
3740 onSurfaceChanged: {
3841 if (surface) {
3942 surfaceItem.surface = surface;
4043 root.hadSurface = false;
44+ switchToKeymap(savedKeymap);
45+ }
46+ }
47+
48+ function switchToKeymap(keymap) {
49+ var finalKeymap = keymap.split("+");
50+ savedKeymap = keymap; // save the keymap in case the surface changes later
51+
52+ if (surface) {
53+ surface.setKeymap(finalKeymap[0], finalKeymap[1] || "");
4154 }
4255 }
4356
qml/Stages/TabletStage.qml+2
@@ -79,6 +79,8 @@ AbstractStage {
7979 }
8080 }
8181
82+ mainAppWindow: priv.focusedAppDelegate ? priv.focusedAppDelegate.appWindow : null
83+
8284 orientationChangesEnabled: priv.mainAppOrientationChangesEnabled
8385
8486 supportedOrientations: {
tests/mocks/AccountsService/AccountsService.cpp+17
@@ -192,3 +192,20 @@ void AccountsService::setEmail(const QString &email)
192192 m_email = email;
193193 Q_EMIT emailChanged();
194194}
195+
196+QStringList AccountsService::keymaps() const
197+{
198+ if (!m_kbdMap.isEmpty()) {
199+ return m_kbdMap;
200+ }
201+
202+ return {QStringLiteral("us")};
203+}
204+
205+void AccountsService::setKeymaps(const QStringList &keymaps)
206+{
207+ if (keymaps != m_kbdMap) {
208+ m_kbdMap = keymaps;
209+ Q_EMIT keymapsChanged();
210+ }
211+}
tests/mocks/AccountsService/AccountsService.h+8
@@ -74,6 +74,10 @@ class AccountsService: public QObject
7474 NOTIFY hereLicensePathChanged)
7575 Q_PROPERTY(QString realName READ realName WRITE setRealName NOTIFY realNameChanged)
7676 Q_PROPERTY(QString email READ email WRITE setEmail NOTIFY emailChanged)
77+ Q_PROPERTY(QStringList keymaps
78+ READ keymaps
79+ WRITE setKeymaps // only in mock
80+ NOTIFY keymapsChanged)
7781
7882public:
7983 enum PasswordDisplayHint {
@@ -110,6 +114,8 @@ public:
110114 void setRealName(const QString &realName);
111115 QString email() const;
112116 void setEmail(const QString &email);
117+ QStringList keymaps() const;
118+ void setKeymaps(const QStringList &keymaps);
113119
114120Q_SIGNALS:
115121 void userChanged();
@@ -125,6 +131,7 @@ Q_SIGNALS:
125131 void hereLicensePathChanged();
126132 void realNameChanged();
127133 void emailChanged();
134+ void keymapsChanged();
128135
129136private:
130137 bool m_enableLauncherWhileLocked;
@@ -138,6 +145,7 @@ private:
138145 bool m_hereEnabled;
139146 QString m_hereLicensePath;
140147 QString m_realName;
148+ QStringList m_kbdMap;
141149 QString m_email;
142150};
143151
tests/mocks/QMenuModel/QDBusActionGroup.qml+5−5
@@ -1,8 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * Authors:
5− * Daniel d'Andrada <daniel.dandrada@canonical.com>
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
63 *
74 * This program is free software; you can redistribute it and/or modify
85 * it under the terms of the GNU General Public License as published by
@@ -18,7 +15,6 @@
1815 */
1916
2017import QtQuick 2.4
21−import Ubuntu.Settings.Menus 0.1 as Menus
2218import QMenuModel 0.1
2319
2420QtObject {
@@ -47,6 +43,10 @@ QtObject {
4743 function activate() {
4844 activated();
4945 }
46+
47+ function updateState(newState) {
48+ state = newState;
49+ }
5050 }", actionGroup);
5151 }
5252}
tests/mocks/Unity/Application/MirSurface.cpp+17−1
@@ -134,7 +134,6 @@ void MirSurface::setOrientationAngle(Mir::OrientationAngle angle)
134134 Q_EMIT orientationAngleChanged(angle);
135135}
136136
137−
138137Mir::ShellChrome MirSurface::shellChrome() const
139138{
140139 return m_shellChrome;
@@ -149,7 +148,24 @@ void MirSurface::setShellChrome(Mir::ShellChrome shellChrome)
149148 Q_EMIT shellChromeChanged(shellChrome);
150149}
151150
151+QString MirSurface::keymapLayout() const
152+{
153+ return m_keyMap.first;
154+}
155+
156+QString MirSurface::keymapVariant() const
157+{
158+ return m_keyMap.second;
159+}
152160
161+void MirSurface::setKeymap(const QString &layout, const QString &variant)
162+{
163+ if (layout.isEmpty()) {
164+ return;
165+ }
166+ m_keyMap = qMakePair(layout, variant);
167+ Q_EMIT keymapChanged(layout, variant);
168+}
153169
154170void MirSurface::registerView(qintptr viewId)
155171{
tests/mocks/Unity/Application/MirSurface.h+5
@@ -75,6 +75,9 @@ public:
7575 int heightIncrement() const override { return m_heightIncrement; }
7676
7777 Mir::ShellChrome shellChrome() const override;
78+ QString keymapLayout() const override;
79+ QString keymapVariant() const override;
80+ Q_INVOKABLE void setKeymap(const QString &layout, const QString &variant) override;
7881
7982 ////
8083 // API for tests
@@ -158,6 +161,8 @@ private:
158161 bool visible;
159162 };
160163 QHash<qintptr, View> m_views;
164+
165+ QPair<QString,QString> m_keyMap; // pair of layout+variant
161166};
162167
163168#endif // MOCK_MIR_SURFACE_H
tests/plugins/AccountsService/PropertiesServer.cpp+9
@@ -25,10 +25,16 @@
2525#include <QDBusMessage>
2626#include <QDBusMetaType>
2727
28+using StringMap = QMap<QString,QString>;
29+using StringMapList = QList<StringMap>;
30+Q_DECLARE_METATYPE(StringMapList)
31+
2832PropertiesServer::PropertiesServer(QObject *parent)
2933 : QObject(parent)
3034{
3135 qDBusRegisterMetaType<QList<QVariantMap>>();
36+ qDBusRegisterMetaType<StringMap>();
37+ qDBusRegisterMetaType<StringMapList>();
3238 Reset();
3339}
3440
@@ -63,6 +69,8 @@ void PropertiesServer::Set(const QString &interface, const QString &property, co
6369 if (interface == QStringLiteral("com.canonical.unity.AccountsService") &&
6470 property == QStringLiteral("LauncherItems")) {
6571 newValue = QVariant::fromValue(qdbus_cast<QList<QVariantMap>>(newValue.value<QDBusArgument>()));
72+ } else if (interface == "org.freedesktop.Accounts.User" && property == "InputSources") {
73+ newValue = QVariant::fromValue(qdbus_cast<StringMapList>(newValue.value<QDBusArgument>()));
6674 }
6775
6876 oldValue = newValue;
@@ -101,4 +109,5 @@ void PropertiesServer::Reset()
101109 m_properties["com.ubuntu.location.providers.here.AccountsService"]["LicenseBasePath"] = "";
102110 m_properties["org.freedesktop.Accounts.User"]["BackgroundFile"] = "";
103111 m_properties["org.freedesktop.Accounts.User"]["RealName"] = "";
112+ m_properties["org.freedesktop.Accounts.User"]["InputSources"] = QVariant::fromValue(StringMapList());
104113}
tests/plugins/AccountsService/PropertiesServer.h−1
@@ -20,7 +20,6 @@
2020#ifndef UNITY_PROPERTIESSERVER_H
2121#define UNITY_PROPERTIESSERVER_H
2222
23−#include "PropertiesServer.h"
2423#include <QDBusContext>
2524#include <QDBusVariant>
2625#include <QObject>
tests/plugins/AccountsService/client.cpp+30
@@ -23,6 +23,11 @@
2323#include <QTest>
2424#include <QDebug>
2525#include <QDBusReply>
26+#include <QDBusMetaType>
27+
28+using StringMap = QMap<QString,QString>;
29+using StringMapList = QList<StringMap>;
30+Q_DECLARE_METATYPE(StringMapList)
2631
2732template <class T>
2833QVariant dbusVariant(const T& value) { return QVariant::fromValue(QDBusVariant(value)); }
@@ -52,6 +57,9 @@ public:
5257
5358 QObject::connect(m_uscInputInterface, SIGNAL(setMousePrimaryButtonCalled(int)),
5459 this, SIGNAL(setMousePrimaryButtonCalled(int)));
60+
61+ qDBusRegisterMetaType<StringMap>();
62+ qDBusRegisterMetaType<StringMapList>();
5563 }
5664
5765private Q_SLOTS:
@@ -303,6 +311,28 @@ private Q_SLOTS:
303311 QCOMPARE(arguments.at(0).toInt(), 0);
304312 }
305313
314+ void testAsynchronousChangeForKeymaps()
315+ {
316+ AccountsService session(this, QTest::currentTestFunction());
317+
318+ QCOMPARE(session.keymaps(), {"us"});
319+
320+ StringMapList inputSources;
321+ StringMap map1;
322+ map1.insert("xkb", "cz+qwerty");
323+ inputSources.append(map1);
324+ StringMap map2;
325+ map2.insert("xkb", "fr");
326+ inputSources.append(map2);
327+
328+ ASSERT_DBUS_CALL(m_userInterface->asyncCall("Set",
329+ "org.freedesktop.Accounts.User",
330+ "InputSources",
331+ QVariant::fromValue(QDBusVariant(QVariant::fromValue(inputSources)))));
332+ QStringList result = {"cz+qwerty", "fr"};
333+ QTRY_COMPARE(session.keymaps(), result);
334+ }
335+
306336Q_SIGNALS:
307337 void propertiesChanged(const QString &interface, const QVariantMap &changed, const QStringList &invalid);
308338 void setMousePrimaryButtonCalled(int button);
tests/qmltests/Stages/tst_DesktopStage.qml−2
@@ -510,8 +510,6 @@ Item {
510510 }
511511
512512 function test_dropShadow() {
513− killAllRunningApps();
514−
515513 // verify the drop shadow is not visible initially
516514 verify(PanelState.dropShadow == false);
517515
tests/qmltests/tst_Shell.qml+64
@@ -2281,5 +2281,69 @@ Rectangle {
22812281 compare(topmostSurfaceItem.touchPressCount, 2);
22822282 compare(topmostSurfaceItem.touchReleaseCount, 2);
22832283 }
2284+
2285+ function test_switchKeymap() {
2286+ // start with phone shell
2287+ loadShell("phone");
2288+ shell.usageScenario = "shell";
2289+ waitForRendering(shell);
2290+ swipeAwayGreeter();
2291+
2292+ // configure keymaps
2293+ AccountsService.keymaps = ["sk", "cz+qwerty", "fr"] // "configure" the keymaps for user
2294+
2295+ // start some app
2296+ var app = ApplicationManager.startApplication("dialer-app");
2297+ waitUntilAppWindowIsFullyLoaded(app);
2298+
2299+ // verify the initial keymap of the newly started app is the first one from the list
2300+ tryCompare(app.session.lastSurface, "keymapLayout", "sk");
2301+ tryCompare(app.session.lastSurface, "keymapVariant", "");
2302+
2303+ // switch to next keymap, should go to "cz+qwerty"
2304+ keyClick(Qt.Key_Space, Qt.MetaModifier);
2305+ tryCompare(app.session.lastSurface, "keymapLayout", "cz");
2306+ tryCompare(app.session.lastSurface, "keymapVariant", "qwerty");
2307+
2308+ // switch to next keymap, should go to "fr"
2309+ keyClick(Qt.Key_Space, Qt.MetaModifier);
2310+
2311+ // go to e.g. desktop stage
2312+ loadShell("desktop");
2313+ shell.usageScenario = "desktop";
2314+ waitForRendering(shell);
2315+
2316+ // start a second app, should get the last configured keyboard, "fr"
2317+ var app2 = ApplicationManager.startApplication("calendar-app");
2318+ waitUntilAppWindowIsFullyLoaded(app2);
2319+ tryCompare(app2.session.lastSurface, "keymapLayout", "fr");
2320+ tryCompare(app2.session.lastSurface, "keymapVariant", "");
2321+
2322+ // focus our first app, make sure it also has the "fr" keymap
2323+ ApplicationManager.requestFocusApplication("dialer-app");
2324+ tryCompare(app.session.lastSurface, "keymapLayout", "fr");
2325+ tryCompare(app.session.lastSurface, "keymapVariant", "");
2326+
2327+ // switch to previous keymap, should be "cz+qwerty"
2328+ keyClick(Qt.Key_Space, Qt.MetaModifier|Qt.ShiftModifier);
2329+ tryCompare(app.session.lastSurface, "keymapLayout", "cz");
2330+ tryCompare(app.session.lastSurface, "keymapVariant", "qwerty");
2331+
2332+ // go next twice to "sk", past the end
2333+ keyClick(Qt.Key_Space, Qt.MetaModifier);
2334+ keyClick(Qt.Key_Space, Qt.MetaModifier);
2335+ tryCompare(app.session.lastSurface, "keymapLayout", "sk");
2336+ tryCompare(app.session.lastSurface, "keymapVariant", "");
2337+
2338+ // go back once to past the beginning, to "fr"
2339+ keyClick(Qt.Key_Space, Qt.MetaModifier|Qt.ShiftModifier);
2340+ tryCompare(app.session.lastSurface, "keymapLayout", "fr");
2341+ tryCompare(app.session.lastSurface, "keymapVariant", "");
2342+
2343+ // switch to app2, should also get "fr"
2344+ ApplicationManager.requestFocusApplication("calendar-app");
2345+ tryCompare(app2.session.lastSurface, "keymapLayout", "fr");
2346+ tryCompare(app2.session.lastSurface, "keymapVariant", "");
2347+ }
22842348 }
22852349}