on xenial help sign in
Implement frontend support for running keyboard indicator

Approved by: Michael Terry

commite01964af0d29bbde85598bbe8f7faa95c7232cab authorLukáš Tinkl <lukas.tinkl@canonical.com> dateWed 3 Aug 2016, 15:47 (10 years ago) parentd406bd84da3d7a
10 files changed+185−17
debian/control+2−1 plugins/Unity/Indicators/indicatorsmanager.cpp+3−5 qml/Components/KeymapSwitcher.qml+27−1 qml/Panel/IndicatorItemRow.qml+13−2 qml/Panel/Indicators/MenuItemFactory.qml+65 tests/mocks/Unity/Indicators/IndicatorsModel.qml+41−1 tests/qmltests/Panel/tst_IndicatorItemRow.qml+26−1 tests/qmltests/Panel/tst_IndicatorsBar.qml+3−3 tests/qmltests/Panel/tst_IndicatorsMenu.qml+4−3 tests/qmltests/tst_OrientedShell.qml+1
debian/control+2−1
@@ -130,7 +130,8 @@ Depends: dmz-cursor-theme,
130130 unity8-private | unity-launcher-impl,
131131 ${misc:Depends},
132132 ${shlibs:Depends},
133−Recommends: indicator-session,
133+Recommends: indicator-keyboard,
134+ indicator-session,
134135 unity-scope-click,
135136 unity-scope-mediascanner2,
136137Breaks: indicator-network (<< 0.5.1+14.10.20141014),
plugins/Unity/Indicators/indicatorsmanager.cpp+3−5
@@ -114,10 +114,6 @@ void IndicatorsManager::loadFile(const QFileInfo& file_info)
114114 QSettings indicator_settings(file_info.absoluteFilePath(), QSettings::IniFormat, this);
115115 const QString name = indicator_settings.value(QStringLiteral("Indicator Service/Name")).toString();
116116
117− if (m_platform.isPC() && name == QLatin1String("indicator-keyboard")) {
118− return; // convergence: skip this indicator until it works in Mir
119− }
120−
121117 auto iter = m_indicatorsData.constFind(name);
122118 if (iter != m_indicatorsData.constEnd())
123119 {
@@ -285,11 +281,13 @@ Indicator::Ptr IndicatorsManager::indicator(const QString& indicator_name)
285281
286282 // convergence:
287283 // 1) enable session indicator
288− // 2) on a PC, switch the battery/power indicator to desktop mode,
284+ // 2) enable keyboard indicator
285+ // 3) on a PC, switch the battery/power indicator to desktop mode,
289286 // can't control brightness for now and phone-on-desktop broken (FIXME)
290287 //
291288 // The rest of the indicators respect their default profile (which is "phone", even on desktop PCs)
292289 if ((new_indicator->identifier() == QStringLiteral("indicator-session"))
290+ || new_indicator->identifier() == QStringLiteral("indicator-keyboard")
293291 || (new_indicator->identifier() == QStringLiteral("indicator-power") && m_platform.isPC())) {
294292 new_indicator->setProfile(m_profile.replace(QStringLiteral("phone"), QStringLiteral("desktop")));
295293 } else {
qml/Components/KeymapSwitcher.qml+27−1
@@ -17,6 +17,7 @@
1717import QtQuick 2.4
1818import AccountsService 0.1
1919import GlobalShortcut 1.0
20+import QMenuModel 0.1
2021import Unity.Application 0.1
2122
2223QtObject {
@@ -36,7 +37,8 @@ QtObject {
3637
3738 readonly property var keymaps: AccountsService.keymaps
3839 readonly property int keymapCount: keymaps.length
39− property int currentKeymapIndex: 0
40+ // default keymap, either the one remembered by the indicator, or the 1st one selected by user
41+ property int currentKeymapIndex: actionGroup.currentAction.valid ? actionGroup.currentAction.state : 0
4042 readonly property string currentKeymap: keymaps[currentKeymapIndex]
4143
4244 function nextKeymap() {
@@ -62,4 +64,28 @@ QtObject {
6264 property: "keymap"
6365 value: root.currentKeymap
6466 }
67+
68+ // indicator
69+ property QDBusActionGroup actionGroup: QDBusActionGroup {
70+ busType: DBus.SessionBus
71+ busName: "com.canonical.indicator.keyboard"
72+ objectPath: "/com/canonical/indicator/keyboard"
73+
74+ property variant currentAction: action("current")
75+ property variant activeAction: action("active")
76+
77+ Component.onCompleted: actionGroup.start();
78+ }
79+
80+ onCurrentKeymapIndexChanged: {
81+ actionGroup.currentAction.updateState(currentKeymapIndex);
82+ }
83+
84+ readonly property int activeActionState: actionGroup.activeAction.valid ? actionGroup.activeAction.state : -1
85+
86+ onActiveActionStateChanged: {
87+ if (activeActionState != -1) {
88+ currentKeymapIndex = activeActionState;
89+ }
90+ }
6591}
qml/Panel/IndicatorItemRow.qml+13−2
@@ -18,6 +18,10 @@ import QtQuick 2.4
1818import QtQuick.Window 2.2
1919import Ubuntu.Components 1.3
2020
21+// for indicator-keyboard
22+import AccountsService 0.1
23+import Unity.InputInfo 0.1
24+
2125Item {
2226 id: root
2327 width: row.width
@@ -127,6 +131,11 @@ Item {
127131 property bool forceAlignmentAnimationDisabled: false
128132 }
129133
134+ InputDeviceModel {
135+ id: keyboardsModel
136+ deviceFilter: InputInfo.Keyboard
137+ }
138+
130139 onCurrentItemChanged: {
131140 if (d.previousItem) {
132141 d.firstItemSwitch = false;
@@ -184,9 +193,11 @@ Item {
184193
185194 property int ownIndex: index
186195 property bool overflow: row.width - x > overFlowWidth
187− property bool hidden: !expanded && (overflow || !indicatorVisible || hideSessionIndicator)
196+ property bool hidden: !expanded && (overflow || !indicatorVisible || hideSessionIndicator || hideKeyboardIndicator)
188197 // HACK for indicator-session
189198 readonly property bool hideSessionIndicator: identifier == "indicator-session" && Math.min(Screen.width, Screen.height) <= units.gu(60)
199+ // HACK for indicator-keyboard
200+ readonly property bool hideKeyboardIndicator: identifier == "indicator-keyboard" && (AccountsService.keymaps.length < 2 || keyboardsModel.count == 0)
190201
191202 height: row.height
192203 expanded: root.expanded
@@ -202,7 +213,7 @@ Item {
202213 NumberAnimation { duration: UbuntuAnimation.SnapDuration; easing: UbuntuAnimation.StandardEasing }
203214 }
204215
205− width: ((expanded || indicatorVisible) && !hideSessionIndicator) ? implicitWidth : 0
216+ width: ((expanded || indicatorVisible) && !hideSessionIndicator && !hideKeyboardIndicator) ? implicitWidth : 0
206217
207218 Behavior on width {
208219 NumberAnimation { duration: UbuntuAnimation.SnapDuration; easing: UbuntuAnimation.StandardEasing }
qml/Panel/Indicators/MenuItemFactory.qml+65
@@ -985,6 +985,62 @@ Item {
985985 }
986986 }
987987
988+ Component {
989+ id: keymapMenu;
990+
991+ // FIXME this should use a "radio button" menu, once we have it in the SDK
992+ ListItems.Empty {
993+ id: checkItem
994+ objectName: "keymapMenu"
995+ property QtObject menuData: null
996+ property int menuIndex: -1
997+ readonly property bool serverChecked: menuData && menuData.isToggled || false
998+
999+ enabled: menuData && menuData.sensitive || false
1000+ highlightWhenPressed: false
1001+ __acceptEvents: !serverChecked
1002+
1003+ CheckBox {
1004+ id: checkbox
1005+ __acceptEvents: !checkItem.serverChecked
1006+ anchors {
1007+ left: parent.left
1008+ leftMargin: checkItem.__contentsMargins
1009+ verticalCenter: parent.verticalCenter
1010+ }
1011+ }
1012+
1013+ ServerPropertySynchroniser {
1014+ objectName: "sync"
1015+ syncTimeout: Utils.Constants.indicatorValueTimeout
1016+
1017+ serverTarget: checkItem
1018+ serverProperty: "serverChecked"
1019+ userTarget: checkbox
1020+ userProperty: "checked"
1021+
1022+ onSyncTriggered: {
1023+ menuModel.activate(checkItem.menuIndex);
1024+ }
1025+ }
1026+
1027+ Label {
1028+ id: label
1029+ anchors {
1030+ left: checkbox.right
1031+ leftMargin: checkItem.__contentsMargins
1032+ right: parent.right
1033+ rightMargin: checkItem.__contentsMargins
1034+ verticalCenter: parent.verticalCenter
1035+ }
1036+ elide: Text.ElideRight
1037+ text: checkItem.menuData && checkItem.menuData.label || ""
1038+ }
1039+
1040+ onClicked: menuModel.activate(menuIndex);
1041+ }
1042+ }
1043+
9881044 function load(modelData, context) {
9891045 // tweak indicator-session items
9901046 if (context === "indicator-session") {
@@ -995,6 +1051,15 @@ Item {
9951051 }
9961052 }
9971053
1054+ // specialize for indicator-keyboard
1055+ if (context === "indicator-keyboard") {
1056+ if (modelData.isRadio) {
1057+ return keymapMenu;
1058+ } else if (modelData.action === "indicator.map" || modelData.action === "indicator.chart") {
1059+ return null; // map and chart not available
1060+ }
1061+ }
1062+
9981063 if (modelData.type !== undefined && modelData.type !== "") {
9991064 var component = undefined;
10001065
tests/mocks/Unity/Indicators/IndicatorsModel.qml+41−1
@@ -16,11 +16,22 @@
1616
1717import QtQuick 2.4
1818import Unity.Indicators 0.1 as Indicators
19+import Unity.InputInfo 0.1
20+import AccountsService 0.1
1921
2022Indicators.FakeIndicatorsModel {
2123 id: root
2224
2325 property var originalModelData: [
26+ {
27+ "identifier": "indicator-keyboard",
28+ "indicatorProperties": {
29+ "enabled": true,
30+ "busName": "com.canonical.indicators.fake0",
31+ "menuObjectPath": "/com/canonical/indicators/fake0",
32+ "actionsObjectPath": "/com/canonical/indicators/fake0"
33+ }
34+ },
2435 {
2536 "identifier": "fake-indicator-bluetooth",
2637 "indicatorProperties": {
@@ -74,13 +85,38 @@ Indicators.FakeIndicatorsModel {
7485 "menuObjectPath": "/com/canonical/indicators/fake6",
7586 "actionsObjectPath": "/com/canonical/indicators/fake6"
7687 }
88+ },
89+ {
90+ "identifier": "fake-indicator-session",
91+ "indicatorProperties": {
92+ "enabled": true,
93+ "busName": "com.canonical.indicators.fake7",
94+ "menuObjectPath": "/com/canonical/indicators/fake7",
95+ "actionsObjectPath": "/com/canonical/indicators/fake7"
96+ }
7797 }
7898 ]
7999
100+ Component.onCompleted: {
101+ // init data for the fake indicator-keyboard
102+ MockInputDeviceBackend.addMockDevice("/indicator_kbd0", InputInfo.Keyboard);
103+ AccountsService.keymaps = ["us", "cs"];
104+ }
105+
106+ Component.onDestruction: {
107+ MockInputDeviceBackend.removeDevice("/indicator_kbd0");
108+ AccountsService.keymaps = ["us"];
109+ }
110+
80111 function load(profile) {
81112 unload();
82113 root.modelData = originalModelData;
83114
115+ Indicators.UnityMenuModelCache.setCachedModelData("/com/canonical/indicators/fake0",
116+ getUnityMenuModelData("indicator-keyboard",
117+ "Czech (F)",
118+ "",
119+ [ "image://theme/input-keyboard-symbolic" ]));
84120 Indicators.UnityMenuModelCache.setCachedModelData("/com/canonical/indicators/fake1",
85121 getUnityMenuModelData("fake-indicator-bluetooth",
86122 "Bluetooth (F)",
@@ -111,6 +147,11 @@ Indicators.FakeIndicatorsModel {
111147 "Upcoming Events (F)",
112148 "12:04",
113149 []));
150+ Indicators.UnityMenuModelCache.setCachedModelData("/com/canonical/indicators/fake7",
151+ getUnityMenuModelData("fake-indicator-session",
152+ "System (F)",
153+ "",
154+ ["image://theme/system-devices-panel"]));
114155 }
115156
116157 function getUnityMenuModelData(identifier, title, label, icons) {
@@ -173,6 +214,5 @@ Indicators.FakeIndicatorsModel {
173214 break;
174215 }
175216 }
176−
177217 }
178218}
tests/qmltests/Panel/tst_IndicatorItemRow.qml+26−1
@@ -21,6 +21,8 @@ import "../../../qml/Panel"
2121import Ubuntu.Components 1.3
2222import Unity.Test 0.1 as UT
2323import Unity.Indicators 0.1 as Indicators
24+import AccountsService 0.1
25+import Unity.InputInfo 0.1
2426
2527IndicatorTest {
2628 id: root
@@ -194,7 +196,7 @@ IndicatorTest {
194196 verify(dataItem !== null);
195197
196198 indicatorsRow.selectItemAt(dataItem.x + dataItem.width/2);
197− compare(indicatorsRow.currentItem, dataItem);
199+ tryCompare(indicatorsRow, "currentItem", dataItem);
198200 }
199201
200202 // tests item default selection (no item at position X)
@@ -285,5 +287,28 @@ IndicatorTest {
285287 // should go back to 0
286288 tryCompare(findChild(indicatorsRow, "highlight"), "highlightCenterOffset", 0);
287289 }
290+
291+ function test_hidingKeyboardIndicator_data() {
292+ return [
293+ { tag: "No keyboard, no keymap", keyboard: false, keymaps: [], hidden: true },
294+ { tag: "No keyboard, one keymap", keyboard: false, keymaps: ["us"], hidden: true },
295+ { tag: "No keyboard, 2 keymaps", keyboard: false, keymaps: ["us", "cs"], hidden: true },
296+ { tag: "Keyboard, no keymap", keyboard: true, keymaps: [], hidden: true },
297+ { tag: "Keyboard, one keymap", keyboard: true, keymaps: ["us"], hidden: true },
298+ { tag: "Keyboard, 2 keymaps", keyboard: true, keymaps: ["us", "cs"], hidden: false }
299+ ];
300+ }
301+
302+ function test_hidingKeyboardIndicator(data) {
303+ var item = findChild(indicatorsRow, "indicator-keyboard-panelItem");
304+ AccountsService.keymaps = data.keymaps;
305+ if (data.keyboard) {
306+ MockInputDeviceBackend.addMockDevice("/indicator_kbd0", InputInfo.Keyboard);
307+ } else {
308+ MockInputDeviceBackend.removeDevice("/indicator_kbd0");
309+ }
310+
311+ compare(item.hidden, data.hidden);
312+ }
288313 }
289314}
tests/qmltests/Panel/tst_IndicatorsBar.qml+3−3
@@ -136,6 +136,7 @@ IndicatorTest {
136136 }
137137
138138 function wait_for_expansion_to_settle() {
139+ waitUntilTransitionsEnd(indicatorsBar);
139140 tryCompare(heightAnimation, "running", false);
140141 wait(UbuntuAnimation.SnapDuration); // put a little extra wait in for things to settle
141142 }
@@ -151,7 +152,6 @@ IndicatorTest {
151152 indicatorsBar.expanded = true;
152153 wait_for_expansion_to_settle();
153154
154−
155155 // mappedPosition contained within mappedRect
156156 tryCompareFunction(function() {
157157 var mappedRect = indicatorsBar.mapFromItem(dataItem, 0, 0, dataItem.width, dataItem.height);
@@ -203,8 +203,8 @@ IndicatorTest {
203203
204204 function test_visibleIndicators_data() {
205205 return [
206− { visible: [true, false, true, false, true, true] },
207− { visible: [false, false, false, false, false, false] }
206+ { visible: [true, false, true, false, true, true, false, true] },
207+ { visible: [false, false, false, false, false, false, true, false] }
208208 ];
209209 }
210210
tests/qmltests/Panel/tst_IndicatorsMenu.qml+4−3
@@ -219,8 +219,8 @@ IndicatorTest {
219219 mappedPosition.x, indicatorsMenu.openedHeight / 2,
220220 true /* beginTouch */, false /* endTouch */);
221221
222− compare(indicatorItemRow.currentItem, indicatorItem,
223− "Incorrect item activated at position " + i);
222+ tryCompare(indicatorItemRow, "currentItem", indicatorItem, undefined /*timeout, default */,
223+ "Incorrect item activated at position " + i);
224224
225225 touchFlick(indicatorItemRow,
226226 mappedPosition.x, indicatorsMenu.openedHeight / 2,
@@ -265,7 +265,8 @@ IndicatorTest {
265265 false /* beginTouch */, false /* endTouch */,
266266 units.gu(50) /* speed */, 5 /* iterations */); // more samples needed for accurate velocity
267267
268− compare(indicatorItemRow.currentItem, firstItem, "First indicator should still be the current item");
268+ tryCompare(indicatorItemRow, "currentItem", firstItem, undefined /* timeout, default */,
269+ "First indicator should still be the current item");
269270 // after waiting in the same spot with touch down, it should update to the next item.
270271 tryCompare(indicatorItemRow, "currentItem", nextItem);
271272
tests/qmltests/tst_OrientedShell.qml+1
@@ -1117,6 +1117,7 @@ Rectangle {
11171117
11181118 function test_attachRemoveInputDevices(data) {
11191119 loadShell("mako")
1120+ MockInputDeviceBackend.removeDevice("/indicator_kbd0");
11201121 var shell = findChild(orientedShell, "shell");
11211122 var inputMethod = findChild(shell, "inputMethod");
11221123