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

Proposed by Michał Sawicz
Status: Merged
Approved by: Michael Terry
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
Michael Terry Approve
Unity8 CI Bot continuous-integration Approve
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.
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
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
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
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

refine test for switching keymaps, move to tst_Shell

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
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

remove activeKeymapIndex, refine tests

2281. By Lukáš Tinkl

remove debug

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
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

Revision history for this message
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)
Revision history for this message
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"}]'

Revision history for this message
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
Revision history for this message
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
Revision history for this message
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
"""

Revision history for this message
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).

Revision history for this message
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