Merge lp:~unity-team/unity8/keymapSwitching into lp:unity8

Proposed by Michał Sawicz on 2016-03-11
Status: Merged
Approved by: Michael Terry on 2016-03-15
Approved revision: 2281
Merged at revision: 2291
Proposed branch: lp:~unity-team/unity8/keymapSwitching
Merge into: lp:unity8
Prerequisite: lp:~unity-team/unity8/shell_chrome
Diff against target: 634 lines (+265/-10)
20 files modified
plugins/AccountsService/AccountsService.cpp (+26/-0)
plugins/AccountsService/AccountsService.h (+6/-0)
qml/Shell.qml (+50/-0)
qml/Stages/AbstractStage.qml (+1/-0)
qml/Stages/ApplicationWindow.qml (+4/-0)
qml/Stages/DesktopStage.qml (+4/-0)
qml/Stages/PhoneStage.qml (+2/-0)
qml/Stages/SpreadDelegate.qml (+1/-0)
qml/Stages/SurfaceContainer.qml (+13/-0)
qml/Stages/TabletStage.qml (+2/-0)
tests/mocks/AccountsService/AccountsService.cpp (+17/-0)
tests/mocks/AccountsService/AccountsService.h (+8/-0)
tests/mocks/QMenuModel/QDBusActionGroup.qml (+5/-5)
tests/mocks/Unity/Application/MirSurface.cpp (+18/-2)
tests/mocks/Unity/Application/MirSurface.h (+5/-0)
tests/plugins/AccountsService/PropertiesServer.cpp (+9/-0)
tests/plugins/AccountsService/PropertiesServer.h (+0/-1)
tests/plugins/AccountsService/client.cpp (+30/-0)
tests/qmltests/Stages/tst_DesktopStage.qml (+0/-2)
tests/qmltests/tst_Shell.qml (+64/-0)
To merge this branch: bzr merge lp:~unity-team/unity8/keymapSwitching
Reviewer Review Type Date Requested Status
Daniel d'Andrada (community) Needs Information on 2016-03-16
Michael Terry 2016-03-11 Approve on 2016-03-15
Unity8 CI Bot continuous-integration 2016-03-11 Approve on 2016-03-14
Review via email: mp+288842@code.launchpad.net

This proposal supersedes a proposal from 2016-03-11.

Commit Message

Keymap switching support

Description of the Change

 * Are there any related MPs required for this MP to build/function as expected? Please list.
    lp:~unity-team/unity-api/kbdLayout
    lp:~unity-team/qtmir/kbdLayout
    lp:~lukas-kde/qtubuntu/kbdLayout

 * Did you perform an exploratory manual test run of your code change and any related functionality?
Will in silo
 * Did you make sure that your branch does not contain spurious tags?
Y
 * If you changed the packaging (debian), did you subscribe the ubuntu-unity team to this MP?
Y
 * If you changed the UI, has there been a design review?
N/A

To post a comment you must log in.
Michael Terry (mterry) wrote : Posted in a previous version of this proposal

Some inline comments. Haven't reviewed test code yet. Am still setting up my phone to test these branches there.

review: Needs Fixing
Unity8 CI Bot (unity8-ci-bot) wrote :

FAILED: Continuous integration, rev:2278
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/695/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/914
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/931
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial/931
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/929
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/929/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial/929
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial/929/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/929
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/929/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial/929
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial/929/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/929
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/929/artifact/output/*zip*/output.zip
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial/929/console

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/695/rebuild

review: Needs Fixing (continuous-integration)
2279. By Lukáš Tinkl on 2016-03-12

refine test for switching keymaps, move to tst_Shell

Michael Terry (mterry) wrote :

As mentioned in telegram, I think we can drop activeKeymap. And the qmltest should test going past the front / end of keymap list. But besides that, it looks fine. Haven't tested yet, waiting for silo to rebuild.

2280. By Lukáš Tinkl on 2016-03-14

remove activeKeymapIndex, refine tests

2281. By Lukáš Tinkl on 2016-03-14

remove debug

Lukáš Tinkl (lukas-kde) wrote :

> As mentioned in telegram, I think we can drop activeKeymap. And the qmltest
> should test going past the front / end of keymap list. But besides that, it
> looks fine. Haven't tested yet, waiting for silo to rebuild.

Fixed both issues

Unity8 CI Bot (unity8-ci-bot) wrote :

PASSED: Continuous integration, rev:2281
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/738/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=vivid+overlay,testname=qmluitests.sh/414
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=xenial,testname=qmluitests.sh/414
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=phone-armhf,release=vivid+overlay,testname=autopilot.sh/414
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/967
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/984
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial/984
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/982
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/982/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial/982
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial/982/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/982
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/982/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial/982
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial/982/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/982
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/982/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial/982
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial/982/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/738/rebuild

review: Approve (continuous-integration)
Michael Terry (mterry) wrote :

Note to self:

sudo gdbus call --system --dest org.freedesktop.Accounts --object-path /org/freedesktop/Accounts/User32011 --method org.freedesktop.Accounts.User.SetInputSources '[{"xkb": "us"}, {"xkb": "zh"}]'

Michael Terry (mterry) wrote :

OK, worked fine in my testing. Modulo a crasher if you give invalid input. (bug 1557634) But that's not a blocker for now.

Thanks!

review: Approve
Daniel d'Andrada (dandrader) wrote :

"""
    function switchToKeymap(keymap) {
        var finalKeymap = keymap.split("+");
        savedKeymap = keymap; // save the keymap in case the surface changes later

        if (surface) {
            surface.setKeymap(finalKeymap[0], finalKeymap[1] || "");
        }
    }
"""

I know I'm late to the party, but as I'm rebasing surface-based WM on top of silo 041 I couldn't help frowning when I saw that.

This kind of imperative work doesn't belong to QML. Can't the surface have a property that takes the full string and does any and all parsing needed behind the scenes?

Something like that:

"""
SurfaceContainer {
    id: root
    property string keymap
    Binding {
        target: surface
        property: "keymap"
        value: root.keymap
    }
}
"""

review: Needs Information
Daniel d'Andrada (dandrader) wrote :

And as a continuation of the idea in my previous comment, in ApplicationWindow:

"""
function switchToKeymap(keymap) {
        sessionContainer.surfaceContainer.switchToKeymap(keymap);
    }
"""

Would be something like:

"""
property alias keymap: sessionContainer.surfaceContainer.keymap
"""

Lukáš Tinkl (lukas-kde) wrote :

Yeah, thanks for the comments, I'm totally aware this will need some rework with surface based WM (this and the switching based on the currently focused _app_, not surface).

Daniel d'Andrada (dandrader) wrote :

My comments are not related to surface-based versus application-based WM. They are about imperative versus declarative code.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'plugins/AccountsService/AccountsService.cpp'
2--- plugins/AccountsService/AccountsService.cpp 2016-03-14 17:35:28 +0000
3+++ plugins/AccountsService/AccountsService.cpp 2016-03-14 17:35:29 +0000
4@@ -36,6 +36,7 @@
5 #define PROP_ENABLE_INDICATORS_WHILE_LOCKED QStringLiteral("EnableIndicatorsWhileLocked")
6 #define PROP_ENABLE_LAUNCHER_WHILE_LOCKED QStringLiteral("EnableLauncherWhileLocked")
7 #define PROP_FAILED_LOGINS QStringLiteral("FailedLogins")
8+#define PROP_INPUT_SOURCES QStringLiteral("InputSources")
9 #define PROP_LICENSE_ACCEPTED QStringLiteral("LicenseAccepted")
10 #define PROP_LICENSE_BASE_PATH QStringLiteral("LicenseBasePath")
11 #define PROP_MOUSE_CURSOR_SPEED QStringLiteral("MouseCursorSpeed")
12@@ -54,6 +55,10 @@
13 #define PROP_TOUCHPAD_TAP_TO_CLICK QStringLiteral("TouchpadTapToClick")
14 #define PROP_TOUCHPAD_TWO_FINGER_SCROLL QStringLiteral("TouchpadTwoFingerScroll")
15
16+using StringMap = QMap<QString,QString>;
17+using StringMapList = QList<StringMap>;
18+Q_DECLARE_METATYPE(StringMapList)
19+
20
21 QVariant primaryButtonConverter(const QVariant &value)
22 {
23@@ -82,6 +87,7 @@
24 registerProperty(IFACE_ACCOUNTS_USER, PROP_BACKGROUND_FILE, QStringLiteral("backgroundFileChanged"));
25 registerProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL, QStringLiteral("emailChanged"));
26 registerProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME, QStringLiteral("realNameChanged"));
27+ registerProperty(IFACE_ACCOUNTS_USER, PROP_INPUT_SOURCES, QStringLiteral("keymapsChanged"));
28 registerProperty(IFACE_LOCATION_HERE, PROP_LICENSE_ACCEPTED, QStringLiteral("hereEnabledChanged"));
29 registerProperty(IFACE_LOCATION_HERE, PROP_LICENSE_BASE_PATH, QStringLiteral("hereLicensePathChanged"));
30 registerProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_LAUNCHER_WHILE_LOCKED, QStringLiteral("enableLauncherWhileLockedChanged"));
31@@ -230,6 +236,26 @@
32 setProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL, email);
33 }
34
35+QStringList AccountsService::keymaps() const
36+{
37+ auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_INPUT_SOURCES);
38+ QDBusArgument arg = value.value<QDBusArgument>();
39+ StringMapList maps = qdbus_cast<StringMapList>(arg);
40+ QStringList simplifiedMaps;
41+
42+ Q_FOREACH(const StringMap &map, maps) {
43+ Q_FOREACH(const QString &entry, map) {
44+ simplifiedMaps.append(entry);
45+ }
46+ }
47+
48+ if (!simplifiedMaps.isEmpty()) {
49+ return simplifiedMaps;
50+ }
51+
52+ return {QStringLiteral("us")};
53+}
54+
55 uint AccountsService::failedLogins() const
56 {
57 return getProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS).toUInt();
58
59=== modified file 'plugins/AccountsService/AccountsService.h'
60--- plugins/AccountsService/AccountsService.h 2016-03-14 17:35:28 +0000
61+++ plugins/AccountsService/AccountsService.h 2016-03-14 17:35:29 +0000
62@@ -20,6 +20,7 @@
63 #include <QHash>
64 #include <QObject>
65 #include <QString>
66+#include <QStringList>
67 #include <QVariant>
68
69 class AccountsServiceDBusAdaptor;
70@@ -68,6 +69,9 @@
71 NOTIFY hereLicensePathChanged)
72 Q_PROPERTY(QString realName READ realName WRITE setRealName NOTIFY realNameChanged)
73 Q_PROPERTY(QString email READ email WRITE setEmail NOTIFY emailChanged)
74+ Q_PROPERTY(QStringList keymaps
75+ READ keymaps
76+ NOTIFY keymapsChanged)
77
78 public:
79 enum PasswordDisplayHint {
80@@ -97,6 +101,7 @@
81 void setRealName(const QString &realName);
82 QString email() const;
83 void setEmail(const QString &email);
84+ QStringList keymaps() const;
85
86 Q_SIGNALS:
87 void userChanged();
88@@ -111,6 +116,7 @@
89 void hereLicensePathChanged();
90 void realNameChanged();
91 void emailChanged();
92+ void keymapsChanged();
93
94 private Q_SLOTS:
95 void onPropertiesChanged(const QString &user, const QString &interface, const QStringList &changed);
96
97=== modified file 'qml/Shell.qml'
98--- qml/Shell.qml 2016-03-14 17:35:28 +0000
99+++ qml/Shell.qml 2016-03-14 17:35:29 +0000
100@@ -102,6 +102,8 @@
101 // internal props from here onwards
102 readonly property var mainApp:
103 applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainApp : null
104+ readonly property var mainAppWindow:
105+ applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainAppWindow : null
106
107 // Disable everything while greeter is waiting, so that the user can't swipe
108 // the greeter or launcher until we know whether the session is locked.
109@@ -736,6 +738,54 @@
110 onMouseMoved: { cursor.opacity = 1; }
111 }
112
113+ // keymap switching
114+ GlobalShortcut {
115+ shortcut: Qt.MetaModifier|Qt.Key_Space
116+ onTriggered: keymapPriv.nextKeymap()
117+ active: keymapPriv.keymapCount > 1
118+ }
119+
120+ GlobalShortcut {
121+ shortcut: Qt.MetaModifier|Qt.ShiftModifier|Qt.Key_Space
122+ onTriggered: keymapPriv.previousKeymap()
123+ active: keymapPriv.keymapCount > 1
124+ }
125+
126+ QtObject {
127+ id: keymapPriv
128+
129+ readonly property var keymaps: AccountsService.keymaps
130+ readonly property int keymapCount: keymaps.length
131+ property int currentKeymapIndex: 0 // the new one that we're setting
132+ onCurrentKeymapIndexChanged: switchToKeymap();
133+
134+ function nextKeymap() {
135+ var nextIndex = 0;
136+
137+ if (currentKeymapIndex !== -1 && currentKeymapIndex < keymapCount - 1) {
138+ nextIndex = currentKeymapIndex + 1;
139+ }
140+ currentKeymapIndex = nextIndex;
141+ }
142+
143+ function previousKeymap() {
144+ var prevIndex = keymapCount - 1;
145+
146+ if (currentKeymapIndex > 0) {
147+ prevIndex = currentKeymapIndex - 1;
148+ }
149+ currentKeymapIndex = prevIndex;
150+ }
151+
152+ function switchToKeymap() {
153+ if (mainAppWindow) {
154+ mainAppWindow.switchToKeymap(keymaps[currentKeymapIndex]);
155+ }
156+ }
157+ }
158+
159+ onMainAppWindowChanged: keymapPriv.switchToKeymap()
160+
161 Rectangle {
162 id: shutdownFadeOutRectangle
163 z: cursor.z + 1
164
165=== modified file 'qml/Stages/AbstractStage.qml'
166--- qml/Stages/AbstractStage.qml 2016-03-14 17:35:28 +0000
167+++ qml/Stages/AbstractStage.qml 2016-03-14 17:35:29 +0000
168@@ -44,6 +44,7 @@
169
170 // To be read from outside
171 property var mainApp: null
172+ property var mainAppWindow: null
173 property int mainAppWindowOrientationAngle: 0
174 property bool orientationChangesEnabled
175 property int supportedOrientations: Qt.PortraitOrientation
176
177=== modified file 'qml/Stages/ApplicationWindow.qml'
178--- qml/Stages/ApplicationWindow.qml 2016-03-14 17:35:28 +0000
179+++ qml/Stages/ApplicationWindow.qml 2016-03-14 17:35:29 +0000
180@@ -39,6 +39,10 @@
181 property int requestedWidth: -1
182 property int requestedHeight: -1
183
184+ function switchToKeymap(keymap) {
185+ sessionContainer.surfaceContainer.switchToKeymap(keymap);
186+ }
187+
188 readonly property int minimumWidth: sessionContainer.surface ? sessionContainer.surface.minimumWidth : 0
189 readonly property int minimumHeight: sessionContainer.surface ? sessionContainer.surface.minimumHeight : 0
190 readonly property int maximumWidth: sessionContainer.surface ? sessionContainer.surface.maximumWidth : 0
191
192=== modified file 'qml/Stages/DesktopStage.qml'
193--- qml/Stages/DesktopStage.qml 2016-03-14 17:35:28 +0000
194+++ qml/Stages/DesktopStage.qml 2016-03-14 17:35:29 +0000
195@@ -40,6 +40,8 @@
196 ? ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
197 : null
198
199+ mainAppWindow: priv.focusedAppDelegate ? priv.focusedAppDelegate.appWindow : null
200+
201 // application windows never rotate independently
202 mainAppWindowOrientationAngle: shellOrientationAngle
203
204@@ -284,6 +286,8 @@
205 property bool visuallyMaximized: false
206 property bool visuallyMinimized: false
207
208+ readonly property alias appWindow: decoratedWindow.window
209+
210 onFocusChanged: {
211 if (focus && ApplicationManager.focusedApplicationId !== appId) {
212 ApplicationManager.focusApplication(appId);
213
214=== modified file 'qml/Stages/PhoneStage.qml'
215--- qml/Stages/PhoneStage.qml 2016-03-14 17:35:28 +0000
216+++ qml/Stages/PhoneStage.qml 2016-03-14 17:35:29 +0000
217@@ -111,6 +111,8 @@
218 ? applicationManager.findApplication(applicationManager.focusedApplicationId)
219 : null
220
221+ mainAppWindow: priv.focusedAppDelegate ? priv.focusedAppDelegate.appWindow : null
222+
223 orientationChangesEnabled: priv.focusedAppOrientationChangesEnabled
224 && !priv.focusedAppDelegateIsDislocated
225 && !(priv.focusedAppDelegate && priv.focusedAppDelegate.xBehavior.running)
226
227=== modified file 'qml/Stages/SpreadDelegate.qml'
228--- qml/Stages/SpreadDelegate.qml 2016-03-14 17:35:28 +0000
229+++ qml/Stages/SpreadDelegate.qml 2016-03-14 17:35:29 +0000
230@@ -37,6 +37,7 @@
231 | Qt.LandscapeOrientation
232 | Qt.InvertedPortraitOrientation
233 | Qt.InvertedLandscapeOrientation
234+ readonly property alias appWindow: appWindow
235
236 // to be set from outside
237 property bool interactive: true
238
239=== modified file 'qml/Stages/SurfaceContainer.qml'
240--- qml/Stages/SurfaceContainer.qml 2016-03-14 17:35:28 +0000
241+++ qml/Stages/SurfaceContainer.qml 2016-03-14 17:35:29 +0000
242@@ -19,6 +19,7 @@
243 import Ubuntu.Gestures 0.1 // For TouchGate
244 import Utils 0.1 // for InputWatcher
245 import Unity.Application 0.1 // for MirSurfaceItem
246+import AccountsService 0.1
247
248 FocusScope {
249 id: root
250@@ -34,10 +35,22 @@
251 property int requestedWidth: -1
252 property int requestedHeight: -1
253
254+ property string savedKeymap: AccountsService.keymaps[0] // start with the user default
255+
256 onSurfaceChanged: {
257 if (surface) {
258 surfaceItem.surface = surface;
259 root.hadSurface = false;
260+ switchToKeymap(savedKeymap);
261+ }
262+ }
263+
264+ function switchToKeymap(keymap) {
265+ var finalKeymap = keymap.split("+");
266+ savedKeymap = keymap; // save the keymap in case the surface changes later
267+
268+ if (surface) {
269+ surface.setKeymap(finalKeymap[0], finalKeymap[1] || "");
270 }
271 }
272
273
274=== modified file 'qml/Stages/TabletStage.qml'
275--- qml/Stages/TabletStage.qml 2016-03-14 17:35:28 +0000
276+++ qml/Stages/TabletStage.qml 2016-03-14 17:35:29 +0000
277@@ -76,6 +76,8 @@
278 }
279 }
280
281+ mainAppWindow: priv.focusedAppDelegate ? priv.focusedAppDelegate.appWindow : null
282+
283 orientationChangesEnabled: priv.mainAppOrientationChangesEnabled
284
285 supportedOrientations: {
286
287=== modified file 'tests/mocks/AccountsService/AccountsService.cpp'
288--- tests/mocks/AccountsService/AccountsService.cpp 2016-03-14 17:35:28 +0000
289+++ tests/mocks/AccountsService/AccountsService.cpp 2016-03-14 17:35:29 +0000
290@@ -172,3 +172,20 @@
291 m_email = email;
292 Q_EMIT emailChanged();
293 }
294+
295+QStringList AccountsService::keymaps() const
296+{
297+ if (!m_kbdMap.isEmpty()) {
298+ return m_kbdMap;
299+ }
300+
301+ return {QStringLiteral("us")};
302+}
303+
304+void AccountsService::setKeymaps(const QStringList &keymaps)
305+{
306+ if (keymaps != m_kbdMap) {
307+ m_kbdMap = keymaps;
308+ Q_EMIT keymapsChanged();
309+ }
310+}
311
312=== modified file 'tests/mocks/AccountsService/AccountsService.h'
313--- tests/mocks/AccountsService/AccountsService.h 2016-03-14 17:35:28 +0000
314+++ tests/mocks/AccountsService/AccountsService.h 2016-03-14 17:35:29 +0000
315@@ -69,6 +69,10 @@
316 NOTIFY hereLicensePathChanged)
317 Q_PROPERTY(QString realName READ realName WRITE setRealName NOTIFY realNameChanged)
318 Q_PROPERTY(QString email READ email WRITE setEmail NOTIFY emailChanged)
319+ Q_PROPERTY(QStringList keymaps
320+ READ keymaps
321+ WRITE setKeymaps // only in mock
322+ NOTIFY keymapsChanged)
323
324 public:
325 enum PasswordDisplayHint {
326@@ -102,6 +106,8 @@
327 void setRealName(const QString &realName);
328 QString email() const;
329 void setEmail(const QString &email);
330+ QStringList keymaps() const;
331+ void setKeymaps(const QStringList &keymaps);
332
333 Q_SIGNALS:
334 void userChanged();
335@@ -116,6 +122,7 @@
336 void hereLicensePathChanged();
337 void realNameChanged();
338 void emailChanged();
339+ void keymapsChanged();
340
341 private:
342 bool m_enableLauncherWhileLocked;
343@@ -128,6 +135,7 @@
344 bool m_hereEnabled;
345 QString m_hereLicensePath;
346 QString m_realName;
347+ QStringList m_kbdMap;
348 QString m_email;
349 };
350
351
352=== modified file 'tests/mocks/QMenuModel/QDBusActionGroup.qml'
353--- tests/mocks/QMenuModel/QDBusActionGroup.qml 2015-07-21 14:38:35 +0000
354+++ tests/mocks/QMenuModel/QDBusActionGroup.qml 2016-03-14 17:35:29 +0000
355@@ -1,8 +1,5 @@
356 /*
357- * Copyright (C) 2013 Canonical, Ltd.
358- *
359- * Authors:
360- * Daniel d'Andrada <daniel.dandrada@canonical.com>
361+ * Copyright (C) 2013-2016 Canonical, Ltd.
362 *
363 * This program is free software; you can redistribute it and/or modify
364 * it under the terms of the GNU General Public License as published by
365@@ -18,7 +15,6 @@
366 */
367
368 import QtQuick 2.4
369-import Ubuntu.Settings.Menus 0.1 as Menus
370 import QMenuModel 0.1
371
372 QtObject {
373@@ -47,6 +43,10 @@
374 function activate() {
375 activated();
376 }
377+
378+ function updateState(newState) {
379+ state = newState;
380+ }
381 }", actionGroup);
382 }
383 }
384
385=== modified file 'tests/mocks/Unity/Application/MirSurface.cpp'
386--- tests/mocks/Unity/Application/MirSurface.cpp 2016-03-14 17:35:28 +0000
387+++ tests/mocks/Unity/Application/MirSurface.cpp 2016-03-14 17:35:29 +0000
388@@ -134,7 +134,6 @@
389 Q_EMIT orientationAngleChanged(angle);
390 }
391
392-
393 Mir::ShellChrome MirSurface::shellChrome() const
394 {
395 return m_shellChrome;
396@@ -149,7 +148,24 @@
397 Q_EMIT shellChromeChanged(shellChrome);
398 }
399
400-
401+QString MirSurface::keymapLayout() const
402+{
403+ return m_keyMap.first;
404+}
405+
406+QString MirSurface::keymapVariant() const
407+{
408+ return m_keyMap.second;
409+}
410+
411+void MirSurface::setKeymap(const QString &layout, const QString &variant)
412+{
413+ if (layout.isEmpty()) {
414+ return;
415+ }
416+ m_keyMap = qMakePair(layout, variant);
417+ Q_EMIT keymapChanged(layout, variant);
418+}
419
420 void MirSurface::registerView(qintptr viewId)
421 {
422
423=== modified file 'tests/mocks/Unity/Application/MirSurface.h'
424--- tests/mocks/Unity/Application/MirSurface.h 2016-03-14 17:35:28 +0000
425+++ tests/mocks/Unity/Application/MirSurface.h 2016-03-14 17:35:29 +0000
426@@ -75,6 +75,9 @@
427 int heightIncrement() const override { return m_heightIncrement; }
428
429 Mir::ShellChrome shellChrome() const override;
430+ QString keymapLayout() const override;
431+ QString keymapVariant() const override;
432+ Q_INVOKABLE void setKeymap(const QString &layout, const QString &variant) override;
433
434 ////
435 // API for tests
436@@ -158,6 +161,8 @@
437 bool visible;
438 };
439 QHash<qintptr, View> m_views;
440+
441+ QPair<QString,QString> m_keyMap; // pair of layout+variant
442 };
443
444 #endif // MOCK_MIR_SURFACE_H
445
446=== modified file 'tests/plugins/AccountsService/PropertiesServer.cpp'
447--- tests/plugins/AccountsService/PropertiesServer.cpp 2016-03-10 22:42:54 +0000
448+++ tests/plugins/AccountsService/PropertiesServer.cpp 2016-03-14 17:35:29 +0000
449@@ -25,10 +25,16 @@
450 #include <QDBusMessage>
451 #include <QDBusMetaType>
452
453+using StringMap = QMap<QString,QString>;
454+using StringMapList = QList<StringMap>;
455+Q_DECLARE_METATYPE(StringMapList)
456+
457 PropertiesServer::PropertiesServer(QObject *parent)
458 : QObject(parent)
459 {
460 qDBusRegisterMetaType<QList<QVariantMap>>();
461+ qDBusRegisterMetaType<StringMap>();
462+ qDBusRegisterMetaType<StringMapList>();
463 Reset();
464 }
465
466@@ -63,6 +69,8 @@
467 if (interface == QStringLiteral("com.canonical.unity.AccountsService") &&
468 property == QStringLiteral("LauncherItems")) {
469 newValue = QVariant::fromValue(qdbus_cast<QList<QVariantMap>>(newValue.value<QDBusArgument>()));
470+ } else if (interface == "org.freedesktop.Accounts.User" && property == "InputSources") {
471+ newValue = QVariant::fromValue(qdbus_cast<StringMapList>(newValue.value<QDBusArgument>()));
472 }
473
474 oldValue = newValue;
475@@ -100,4 +108,5 @@
476 m_properties["com.ubuntu.location.providers.here.AccountsService"]["LicenseBasePath"] = "";
477 m_properties["org.freedesktop.Accounts.User"]["BackgroundFile"] = "";
478 m_properties["org.freedesktop.Accounts.User"]["RealName"] = "";
479+ m_properties["org.freedesktop.Accounts.User"]["InputSources"] = QVariant::fromValue(StringMapList());
480 }
481
482=== modified file 'tests/plugins/AccountsService/PropertiesServer.h'
483--- tests/plugins/AccountsService/PropertiesServer.h 2016-02-09 13:58:35 +0000
484+++ tests/plugins/AccountsService/PropertiesServer.h 2016-03-14 17:35:29 +0000
485@@ -20,7 +20,6 @@
486 #ifndef UNITY_PROPERTIESSERVER_H
487 #define UNITY_PROPERTIESSERVER_H
488
489-#include "PropertiesServer.h"
490 #include <QDBusContext>
491 #include <QDBusVariant>
492 #include <QObject>
493
494=== modified file 'tests/plugins/AccountsService/client.cpp'
495--- tests/plugins/AccountsService/client.cpp 2016-02-25 10:57:17 +0000
496+++ tests/plugins/AccountsService/client.cpp 2016-03-14 17:35:29 +0000
497@@ -23,6 +23,11 @@
498 #include <QTest>
499 #include <QDebug>
500 #include <QDBusReply>
501+#include <QDBusMetaType>
502+
503+using StringMap = QMap<QString,QString>;
504+using StringMapList = QList<StringMap>;
505+Q_DECLARE_METATYPE(StringMapList)
506
507 template <class T>
508 QVariant dbusVariant(const T& value) { return QVariant::fromValue(QDBusVariant(value)); }
509@@ -52,6 +57,9 @@
510
511 QObject::connect(m_uscInputInterface, SIGNAL(setMousePrimaryButtonCalled(int)),
512 this, SIGNAL(setMousePrimaryButtonCalled(int)));
513+
514+ qDBusRegisterMetaType<StringMap>();
515+ qDBusRegisterMetaType<StringMapList>();
516 }
517
518 private Q_SLOTS:
519@@ -266,6 +274,28 @@
520 QCOMPARE(arguments.at(0).toInt(), 0);
521 }
522
523+ void testAsynchronousChangeForKeymaps()
524+ {
525+ AccountsService session(this, QTest::currentTestFunction());
526+
527+ QCOMPARE(session.keymaps(), {"us"});
528+
529+ StringMapList inputSources;
530+ StringMap map1;
531+ map1.insert("xkb", "cz+qwerty");
532+ inputSources.append(map1);
533+ StringMap map2;
534+ map2.insert("xkb", "fr");
535+ inputSources.append(map2);
536+
537+ ASSERT_DBUS_CALL(m_userInterface->asyncCall("Set",
538+ "org.freedesktop.Accounts.User",
539+ "InputSources",
540+ QVariant::fromValue(QDBusVariant(QVariant::fromValue(inputSources)))));
541+ QStringList result = {"cz+qwerty", "fr"};
542+ QTRY_COMPARE(session.keymaps(), result);
543+ }
544+
545 Q_SIGNALS:
546 void propertiesChanged(const QString &interface, const QVariantMap &changed, const QStringList &invalid);
547 void setMousePrimaryButtonCalled(int button);
548
549=== modified file 'tests/qmltests/Stages/tst_DesktopStage.qml'
550--- tests/qmltests/Stages/tst_DesktopStage.qml 2016-02-15 17:18:15 +0000
551+++ tests/qmltests/Stages/tst_DesktopStage.qml 2016-03-14 17:35:29 +0000
552@@ -510,8 +510,6 @@
553 }
554
555 function test_dropShadow() {
556- killAllRunningApps();
557-
558 // verify the drop shadow is not visible initially
559 verify(PanelState.dropShadow == false);
560
561
562=== modified file 'tests/qmltests/tst_Shell.qml'
563--- tests/qmltests/tst_Shell.qml 2016-03-14 17:35:28 +0000
564+++ tests/qmltests/tst_Shell.qml 2016-03-14 17:35:29 +0000
565@@ -2274,5 +2274,69 @@
566 compare(topmostSurfaceItem.touchPressCount, 2);
567 compare(topmostSurfaceItem.touchReleaseCount, 2);
568 }
569+
570+ function test_switchKeymap() {
571+ // start with phone shell
572+ loadShell("phone");
573+ shell.usageScenario = "shell";
574+ waitForRendering(shell);
575+ swipeAwayGreeter();
576+
577+ // configure keymaps
578+ AccountsService.keymaps = ["sk", "cz+qwerty", "fr"] // "configure" the keymaps for user
579+
580+ // start some app
581+ var app = ApplicationManager.startApplication("dialer-app");
582+ waitUntilAppWindowIsFullyLoaded(app);
583+
584+ // verify the initial keymap of the newly started app is the first one from the list
585+ tryCompare(app.session.lastSurface, "keymapLayout", "sk");
586+ tryCompare(app.session.lastSurface, "keymapVariant", "");
587+
588+ // switch to next keymap, should go to "cz+qwerty"
589+ keyClick(Qt.Key_Space, Qt.MetaModifier);
590+ tryCompare(app.session.lastSurface, "keymapLayout", "cz");
591+ tryCompare(app.session.lastSurface, "keymapVariant", "qwerty");
592+
593+ // switch to next keymap, should go to "fr"
594+ keyClick(Qt.Key_Space, Qt.MetaModifier);
595+
596+ // go to e.g. desktop stage
597+ loadShell("desktop");
598+ shell.usageScenario = "desktop";
599+ waitForRendering(shell);
600+
601+ // start a second app, should get the last configured keyboard, "fr"
602+ var app2 = ApplicationManager.startApplication("calendar-app");
603+ waitUntilAppWindowIsFullyLoaded(app2);
604+ tryCompare(app2.session.lastSurface, "keymapLayout", "fr");
605+ tryCompare(app2.session.lastSurface, "keymapVariant", "");
606+
607+ // focus our first app, make sure it also has the "fr" keymap
608+ ApplicationManager.requestFocusApplication("dialer-app");
609+ tryCompare(app.session.lastSurface, "keymapLayout", "fr");
610+ tryCompare(app.session.lastSurface, "keymapVariant", "");
611+
612+ // switch to previous keymap, should be "cz+qwerty"
613+ keyClick(Qt.Key_Space, Qt.MetaModifier|Qt.ShiftModifier);
614+ tryCompare(app.session.lastSurface, "keymapLayout", "cz");
615+ tryCompare(app.session.lastSurface, "keymapVariant", "qwerty");
616+
617+ // go next twice to "sk", past the end
618+ keyClick(Qt.Key_Space, Qt.MetaModifier);
619+ keyClick(Qt.Key_Space, Qt.MetaModifier);
620+ tryCompare(app.session.lastSurface, "keymapLayout", "sk");
621+ tryCompare(app.session.lastSurface, "keymapVariant", "");
622+
623+ // go back once to past the beginning, to "fr"
624+ keyClick(Qt.Key_Space, Qt.MetaModifier|Qt.ShiftModifier);
625+ tryCompare(app.session.lastSurface, "keymapLayout", "fr");
626+ tryCompare(app.session.lastSurface, "keymapVariant", "");
627+
628+ // switch to app2, should also get "fr"
629+ ApplicationManager.requestFocusApplication("calendar-app");
630+ tryCompare(app2.session.lastSurface, "keymapLayout", "fr");
631+ tryCompare(app2.session.lastSurface, "keymapVariant", "");
632+ }
633 }
634 }

Subscribers

People subscribed via source and target branches