on xenial help sign in
Greeter redesign (#401)

* Keyboard height test for the greeter * Scrolling on LoginList changes the user to log in - Fixes: https://github.com/ubports/unity8/issues/397 * Fixes for drag test * [WIP] Style changes * Keyboard detection on greeter prompt - Add keyboard detection for the keyboard button in the GreeterPrompt. The button will now be shown only if an external keyboard is attached. - Add tests for keyboard detection - Add button to change lightDM mode in the OrientedShell tests. This allows us to show the login screen when testing. * [WIP] Pin screen changes - Pin is shown with filled dots - Cursor is hidden on the pin field - Disabled text selection for both pin and password - Disabled text cursor moving for pin * Separate prompts Separate the GreeterPrompt in PinPrompt, TextPrompt and ButtonPrompt depending on the login method. * Blur effect on login container - Add blur to login container - Darken background in WideView - Press enter or space to dismiss CoverPage in NarrowView * Move session button to the bottom right - Session button moved from LoginList to WideView Note: Session button is not available on NarrowView anymore * Fix tests * Fix keyboard detection Fix keyboard detection for greeter prompts after separation by prompt type * Prevent sending signal before updating pin Fix an issue where the login signal is sent before the password/pin property is updated in the Greeter prompt. * Fix fingerprint login * Increase pin prompt size * Set colors for SessionsList * DIsable PinPrompt error color * Fix whitespace * Fix whitespace test * Code improvements * Style improvements - Fix text and icon proportions * Fix focus issue

commit4eca819e2cfd2b9c01bae10d603865cea68c28e5 authorCapsia <43005909+capsia37@users.noreply.github.com> dateTue 23 Nov 2021, 11:49 (5 years ago) parent72b1dc2
16 files changed+800−327
qml/Greeter/ButtonPrompt.qml+51 qml/Greeter/CoverPage.qml+29 qml/Greeter/Greeter.qml+16 qml/Greeter/GreeterPrompt.qml+63−186 qml/Greeter/LoginAreaContainer.qml+3−3 qml/Greeter/LoginList.qml+68−97 qml/Greeter/NarrowView.qml+9−6 qml/Greeter/PinPrompt.qml+184 qml/Greeter/PromptList.qml+7−1 qml/Greeter/SessionsList.qml+3−2 qml/Greeter/TextPrompt.qml+198 qml/Greeter/WideView.qml+75−4 qml/Shell.qml+3−1 tests/qmltests/Greeter/tst_WideView.qml+37−17 tests/qmltests/tst_OrientedShell.qml+44−3 tests/qmltests/tst_Shell.qml+10−7
qml/Greeter/ButtonPrompt.qmlnew file+51
@@ -0,0 +1,51 @@
1+import QtQuick 2.4
2+import Ubuntu.Components 1.3
3+import "../Components"
4+
5+FocusScope {
6+ id: root
7+ objectName: "promptButton"
8+
9+ property alias text: buttonLabel.text
10+ property alias interactive: root.enabled
11+ property bool isSecret
12+ property bool loginError: false
13+ property bool hasKeyboard: false
14+ property string enteredText: ""
15+
16+ signal clicked()
17+ signal canceled()
18+ signal accepted(string response)
19+
20+ Keys.onSpacePressed: clicked();
21+ Keys.onReturnPressed: clicked();
22+ Keys.onEnterPressed: clicked();
23+
24+ anchors.fill: parent
25+
26+ activeFocusOnTab: true
27+
28+ Rectangle {
29+ anchors.fill: parent
30+ radius: units.gu(0.5)
31+ color: "#7A111111"
32+ Behavior on border.color {
33+ ColorAnimation{}
34+ }
35+ border {
36+ color: root.loginError ? theme.palette.normal.negative : theme.palette.normal.raisedSecondaryText
37+ width: root.loginError ? units.dp(2): units.dp(1)
38+ }
39+ }
40+
41+ MouseArea {
42+ anchors.fill: parent
43+ onClicked: parent.clicked();
44+ }
45+
46+ Label {
47+ id: buttonLabel
48+ anchors.centerIn: parent
49+ color: theme.palette.normal.raisedSecondaryText
50+ }
51+}
qml/Greeter/CoverPage.qml+29
@@ -16,6 +16,7 @@
1616 */
1717
1818import QtQuick 2.4
19+import QtGraphicalEffects 1.12
1920import Ubuntu.Components 1.3
2021import Ubuntu.Gestures 0.1
2122import "../Components"
@@ -34,6 +35,11 @@ Showable {
3435
3536 property alias infographics: infographics
3637
38+ property alias blurAreaHeight: loginBoxEffects.height
39+ property alias blurAreaWidth: loginBoxEffects.width
40+ property alias blurAreaX: loginBoxEffects.x
41+ property alias blurAreaY: loginBoxEffects.y
42+
3743 readonly property real showProgress: MathUtils.clamp((width - Math.abs(x + launcherOffset)) / width, 0, 1)
3844
3945 signal tease()
@@ -93,6 +99,29 @@ Showable {
9399 }
94100 }
95101
102+ Rectangle {
103+ id: loginBoxEffects
104+ color: "transparent"
105+ }
106+
107+ ShaderEffectSource {
108+ id: effectSource
109+
110+ sourceItem: greeterBackground
111+ anchors.centerIn: loginBoxEffects
112+ width: loginBoxEffects.width
113+ height: loginBoxEffects.height
114+ sourceRect: Qt.rect(x,y, width, height)
115+ }
116+
117+ FastBlur {
118+ visible: !draggable
119+ anchors.fill: effectSource
120+ source: effectSource
121+ radius: 64
122+ transparentBorder: true
123+ }
124+
96125 // Darkens wallpaper so that we can read text on it and see infographic
97126 Rectangle {
98127 id: backgroundShade
qml/Greeter/Greeter.qml+16
@@ -66,6 +66,10 @@ Showable {
6666
6767 readonly property bool animating: loader.item ? loader.item.animating : false
6868
69+ property rect inputMethodRect
70+
71+ property bool hasKeyboard: false
72+
6973 signal tease()
7074 signal sessionStarted()
7175 signal emergencyCall()
@@ -460,6 +464,18 @@ Showable {
460464 property: "infographicModel"
461465 value: LightDMService.infographic
462466 }
467+
468+ Binding {
469+ target: loader.item
470+ property: "inputMethodRect"
471+ value: root.inputMethodRect
472+ }
473+
474+ Binding {
475+ target: loader.item
476+ property: "hasKeyboard"
477+ value: root.hasKeyboard
478+ }
463479 }
464480
465481 Connections {
qml/Greeter/GreeterPrompt.qml+63−186
@@ -29,222 +29,99 @@ FocusScope {
2929 property string text
3030 property bool isSecret
3131 property bool interactive: true
32− readonly property alias enteredText: passwordInput.text
32+ property bool loginError: false
33+ readonly property string enteredText: loader.item.enteredText
34+ property bool hasKeyboard: false
35+ property bool waitingToAccept: false
3336
3437 signal clicked()
3538 signal canceled()
3639 signal accepted()
3740
38− function showFakePassword() {
39− // Just a silly hack for looking like 4 pin numbers got entered, if
40− // a fingerprint was used and we happen to be using a pin. This was
41− // a request from Design.
42− if (isSecret && isPrompt && !isAlphanumeric) {
43− passwordInput.text = "...."; // actual text doesn't matter
44− }
45− }
46−
4741 GSettings {
4842 id: unity8Settings
4943 schema.id: "com.canonical.Unity8"
5044 }
5145
52− StyledItem {
53− id: d
54−
55− readonly property color textColor: passwordInput.enabled ? theme.palette.normal.raisedText
56− : theme.palette.disabled.raisedText
57− readonly property color selectedColor: passwordInput.enabled ? theme.palette.normal.raised
58− : theme.palette.disabled.raised
59− readonly property color drawColor: passwordInput.enabled ? theme.palette.normal.raisedSecondaryText
60− : theme.palette.disabled.raisedSecondaryText
61− readonly property color errorColor: passwordInput.enabled ? theme.palette.normal.negative
62− : theme.palette.disabled.negative
63− }
46+ onEnteredTextChanged: if (waitingToAccept) root.accepted()
6447
65− Rectangle {
66− anchors.fill: parent
67− border.width: units.dp(1)
68− border.color: d.drawColor
69− radius: units.gu(0.5)
70− color: "transparent"
71− }
72−
73− Component.onCompleted: updateFocus()
74− onIsPromptChanged: updateFocus()
75− function updateFocus() {
76− if (root.isPrompt) {
77− passwordInput.focus = true;
78− } else {
79− promptButton.focus = true;
48+ function showFakePassword() {
49+ // Just a silly hack for looking like 4 pin numbers got entered, if
50+ // a fingerprint was used and we happen to be using a pin. This was
51+ // a request from Design.
52+ if (isSecret) {
53+ loader.item.enteredText = "...."; // actual text doesn't matter
8054 }
8155 }
8256
83− StyledItem {
84− id: promptButton
85− objectName: "promptButton"
86− anchors.fill: parent
87− visible: !root.isPrompt
88− activeFocusOnTab: true
57+ Loader {
58+ id: loader
59+ objectName: "promptLoader"
8960
90− styleName: "FocusShape"
61+ focus: true
9162
92− function triggered() {
93− if (root.interactive) {
94− root.clicked();
95− }
96− }
63+ anchors.fill: parent
9764
98− Rectangle {
99− height: parent.height;
100− width: parent.width
101− color: "transparent"
102− border {
103− color: d.textColor
104− width: units.dp(1)
65+ Connections {
66+ target: loader.item
67+ onClicked: root.clicked()
68+ onCanceled: root.canceled()
69+ onAccepted: {
70+ if (response == enteredText)
71+ root.accepted();
72+ else
73+ waitingToAccept = true;
10574 }
10675 }
10776
108− Keys.onSpacePressed: triggered();
109− Keys.onReturnPressed: triggered();
110− Keys.onEnterPressed: triggered();
111− MouseArea {
112− anchors.fill: parent
113− onClicked: parent.triggered();
77+ Binding {
78+ target: loader.item
79+ property: "text"
80+ value: root.text
11481 }
11582
116− Label {
117− anchors.centerIn: parent
118− color: d.textColor
119− text: root.text
83+ Binding {
84+ target: loader.item
85+ property: "isSecret"
86+ value: root.isSecret
12087 }
121− }
122−
123− TextField {
124− id: passwordInput
125− objectName: "promptField"
126− anchors.fill: parent
127− visible: root.isPrompt
128− opacity: fakeLabel.visible ? 0 : 1
129− activeFocusOnTab: true
13088
131− validator: RegExpValidator {
132− regExp: root.isAlphanumeric ? /^.*$/ : /^\d{4}$/
89+ Binding {
90+ target: loader.item
91+ property: "interactive"
92+ value: root.interactive
13393 }
13494
135− inputMethodHints: Qt.ImhSensitiveData | Qt.ImhNoPredictiveText |
136− Qt.ImhMultiLine | // so OSK doesn't close on Enter
137− (root.isAlphanumeric ? Qt.ImhNone : Qt.ImhDigitsOnly)
138− echoMode: root.isSecret ? TextInput.Password : TextInput.Normal
139− hasClearButton: false
140−
141− readonly property real frameSpacing: units.gu(0.5)
142−
143− style: StyledItem {
144− anchors.fill: parent
145− styleName: "FocusShape"
146−
147− // Properties needed by TextField
148− readonly property color color: d.textColor
149− readonly property color selectedTextColor: d.selectedColor
150− readonly property color selectionColor: d.textColor
151− readonly property color borderColor: "transparent"
152− readonly property color backgroundColor: "transparent"
153− readonly property color errorColor: d.errorColor
154− readonly property real frameSpacing: styledItem.frameSpacing
155−
156− // Properties needed by FocusShape
157− readonly property bool enabled: styledItem.enabled
158− readonly property bool keyNavigationFocus: styledItem.keyNavigationFocus
159− property bool activeFocusOnTab
95+ Binding {
96+ target: loader.item
97+ property: "loginError"
98+ value: root.loginError
16099 }
161100
162− secondaryItem: [
163− Row {
164− id: extraIcons
165− spacing: passwordInput.frameSpacing
166− Icon {
167− name: "keyboard-caps-enabled"
168− height: units.gu(3)
169− width: units.gu(3)
170− color: d.textColor
171− visible: root.isSecret && false // TODO: detect when caps lock is on
172− }
173− Icon {
174− name: "input-keyboard-symbolic"
175− height: units.gu(3)
176− width: units.gu(3)
177− color: d.textColor
178− visible: !unity8Settings.alwaysShowOsk
179− MouseArea {
180− anchors.fill: parent
181− onClicked: unity8Settings.alwaysShowOsk = true
182− }
183− }
184− }
185− ]
186−
187− onDisplayTextChanged: {
188− // We use onDisplayTextChanged instead of onTextChanged because
189− // displayText changes after text and if we did this before it
190− // updated, we would use the wrong displayText for fakeLabel.
191− if (!isAlphanumeric && text.length >= 4) {
192− // hard limit of 4 for passcodes right now
193− respond();
194− }
101+ Binding {
102+ target: loader.item
103+ property: "hasKeyboard"
104+ value: root.hasKeyboard
195105 }
196106
197− onAccepted: respond()
198−
199− function respond() {
200− if (root.interactive) {
201− root.accepted();
202− }
203− }
204−
205− Keys.onEscapePressed: {
206− root.canceled();
207− event.accepted = true;
208− }
209−
210− // We use our own custom placeholder label instead of the standard
211− // TextField one because the standard one hardcodes baseText as the
212− // palette color, whereas we want raisedSecondaryText.
213− Label {
214− id: hint
215− objectName: "promptHint"
216− anchors {
217− left: parent ? parent.left : undefined
218− right: parent ? parent.right : undefined
219− verticalCenter: parent ? parent.verticalCenter : undefined
220− leftMargin: units.gu(1.5)
221− rightMargin: anchors.leftMargin + extraIcons.width
222− }
223− text: root.text
224− visible: passwordInput.text == "" && !passwordInput.inputMethodComposing
225− enabled: visible
226− color: d.drawColor
227− elide: Text.ElideRight
228− }
107+ onLoaded: loader.item.focus = true
229108 }
230109
231− // Have a fake label that covers the text field after the user presses
232− // enter. What we *really* want is a disabled mode that doesn't lose OSK
233− // focus. Because our goal here is simply to keep the OSK up while
234− // we wait for PAM to get back to us, and while waiting, we don't want
235− // the user to be able to edit the field (simply because it would look
236− // weird if we allowed that). But until we have such a disabled mode,
237− // we'll fake it by covering the real text field with a label.
238− FadingLabel {
239− id: fakeLabel
240− anchors.verticalCenter: parent ? parent.verticalCenter : undefined
241− anchors.left: parent ? parent.left : undefined
242− anchors.right: parent ? parent.right : undefined
243− anchors.leftMargin: passwordInput.frameSpacing * 2
244− anchors.rightMargin: passwordInput.frameSpacing * 2 + extraIcons.width
245− color: d.drawColor
246− text: passwordInput.displayText
247− visible: root.isPrompt && !root.interactive
248− enabled: visible
249− }
110+ states: [
111+ State {
112+ name: "ButtonPrompt"
113+ when: !root.isPrompt
114+ PropertyChanges { target: loader; source: "ButtonPrompt.qml" }
115+ },
116+ State {
117+ name: "PinPrompt"
118+ when: root.isPrompt && !root.isAlphanumeric && root.isSecret
119+ PropertyChanges { target: loader; source: "PinPrompt.qml" }
120+ },
121+ State {
122+ name: "TextPrompt"
123+ when: root.isPrompt
124+ PropertyChanges { target: loader; source: "TextPrompt.qml" }
125+ }
126+ ]
250127}
qml/Greeter/LoginAreaContainer.qml+3−3
@@ -21,18 +21,18 @@ Item {
2121 BorderImage {
2222 anchors {
2323 fill: parent
24− topMargin: -units.gu(1)
24+ topMargin: -units.gu(3)
2525 leftMargin: -units.gu(1.5)
2626 rightMargin: -units.gu(1.5)
2727 bottomMargin: -units.gu(1.5)
2828 }
2929 source: "../graphics/dropshadow2gu.sci"
30− opacity: 0.35
30+ opacity: 0
3131 }
3232
3333 UbuntuShape {
3434 anchors.fill: parent
3535 aspect: UbuntuShape.Flat
36− backgroundColor: theme.palette.normal.raised
36+ backgroundColor: "transparent"
3737 }
3838}
qml/Greeter/LoginList.qml+68−97
@@ -26,6 +26,7 @@ StyledItem {
2626
2727 property alias model: userList.model
2828 property alias alphanumeric: promptList.alphanumeric
29+ property alias hasKeyboard: promptList.hasKeyboard
2930 property int currentIndex
3031 property bool locked
3132 property bool waiting
@@ -39,6 +40,8 @@ StyledItem {
3940 property string currentSession // Initially set by LightDM
4041 readonly property string currentUser: userList.currentItem.username
4142
43+ readonly property alias currentUserIndex: userList.currentIndex
44+
4245 signal responded(string response)
4346 signal selected(int index)
4447 signal sessionChooserButtonClicked()
@@ -48,6 +51,7 @@ StyledItem {
4851 }
4952
5053 function showError() {
54+ promptList.loginError = true;
5155 wrongPasswordAnimation.start();
5256 }
5357
@@ -79,6 +83,7 @@ StyledItem {
7983
8084 onCurrentIndexChanged: {
8185 userList.currentIndex = currentIndex;
86+ promptList.loginError = false;
8287 }
8388
8489 LoginAreaContainer {
@@ -93,18 +98,6 @@ StyledItem {
9398
9499 height: Math.max(units.gu(15), promptList.height + units.gu(8))
95100 Behavior on height { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
96−
97− Label {
98− // HACK: Work around https://github.com/ubports/unity8/issues/185
99− text: _realName ? _realName : LightDMService.greeter.authenticationUser
100− visible: userList.count == 1
101− anchors {
102− left: parent.left
103− top: parent.top
104− topMargin: units.gu(2)
105− leftMargin: units.gu(2)
106− }
107− }
108101 }
109102
110103 ListView {
@@ -115,14 +108,16 @@ StyledItem {
115108 anchors.leftMargin: units.gu(2)
116109 anchors.rightMargin: units.gu(2)
117110
118− preferredHighlightBegin: highlightItem.y
119− preferredHighlightEnd: highlightItem.y
111+ preferredHighlightBegin: highlightItem.y + units.gu(1.5)
112+ preferredHighlightEnd: highlightItem.y + units.gu(1.5)
120113 highlightRangeMode: ListView.StrictlyEnforceRange
121114 highlightMoveDuration: root.moveDuration
122115 interactive: count > 1
123116
124117 readonly property bool movingInternally: moveTimer.running || userList.moving
125118
119+ onMovingChanged: if (!moving) root.selected(currentIndex)
120+
126121 onCurrentIndexChanged: {
127122 moveTimer.start();
128123 }
@@ -132,6 +127,7 @@ StyledItem {
132127 height: root.cellHeight
133128
134129 readonly property bool belowHighlight: (userList.currentIndex < 0 && index > 0) || (userList.currentIndex >= 0 && index > userList.currentIndex)
130+ readonly property bool aboveCurrent: (userList.currentIndex > 0 && index < 0) || (userList.currentIndex >= 0 && index < userList.currentIndex)
135131 readonly property int belowOffset: root.highlightedHeight - root.cellHeight
136132 readonly property string userSession: session
137133 readonly property string username: name
@@ -154,41 +150,75 @@ StyledItem {
154150 return 1 - Math.min(1, (Math.abs(highlightDist) + root.cellHeight) / ((root.numAboveBelow + 1) * root.cellHeight))
155151 }
156152
157− FadingLabel {
158− objectName: "username" + index
159− visible: userList.count != 1 // HACK Hide username label until someone sorts out the anchoring with the keyboard-dismiss animation, Work around https://github.com/ubports/unity8/issues/185
153+ Row {
154+ spacing: units.gu(1)
155+// visible: userList.count != 1 // HACK Hide username label until someone sorts out the anchoring with the keyboard-dismiss animation, Work around https://github.com/ubports/unity8/issues/185
160156
161157 anchors {
162− left: parent.left
163158 leftMargin: units.gu(2)
164− right: parent.right
165159 rightMargin: units.gu(2)
160+ horizontalCenter: parent.horizontalCenter
166161 bottom: parent.top
167162 // Add an offset to bottomMargin for any items below the highlight
168− bottomMargin: -(units.gu(4) + (parent.belowHighlight ? parent.belowOffset : 0))
163+ bottomMargin: -(units.gu(4) + (parent.belowHighlight ? parent.belowOffset : parent.aboveCurrent ? -units.gu(5) : 0))
169164 }
170− text: userList.currentIndex === index
171− && name === "*other"
172− && LightDMService.greeter.authenticationUser !== ""
173− ? LightDMService.greeter.authenticationUser : realName
174− color: userList.currentIndex !== index ? theme.palette.normal.raised
175− : theme.palette.normal.raisedText
176−
177− Component.onCompleted: _realName = realName
178−
179− Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
180165
181166 Rectangle {
182167 id: activeIndicator
183− anchors.horizontalCenter: parent.left
184− anchors.horizontalCenterOffset: -units.gu(1)
185168 anchors.verticalCenter: parent.verticalCenter
186− color: userList.currentIndex !== index ? theme.palette.normal.raised
187− : theme.palette.normal.focus
169+ color: theme.palette.normal.raised
188170 visible: userList.count > 1 && loggedIn
189− height: units.gu(0.5)
171+ height: visible ? units.gu(0.5) : 0
190172 width: height
191173 }
174+
175+ Icon {
176+ id: userIcon
177+ name: "account"
178+ height: userList.currentIndex === index ? units.gu(4.5) : units.gu(3)
179+ width: height
180+ color: theme.palette.normal.raisedSecondaryText
181+ anchors.verticalCenter: parent.verticalCenter
182+ }
183+
184+ Column {
185+ anchors.verticalCenter: parent.verticalCenter
186+ spacing: units.gu(0.25)
187+
188+ FadingLabel {
189+ objectName: "username" + index
190+
191+ text: userList.currentIndex === index
192+ && name === "*other"
193+ && LightDMService.greeter.authenticationUser !== ""
194+ ? LightDMService.greeter.authenticationUser : realName
195+ color: userList.currentIndex !== index ? theme.palette.normal.raised
196+ : theme.palette.normal.raisedSecondaryText
197+ font.weight: userList.currentIndex === index ? Font.Normal : Font.Light
198+ font.pointSize: units.gu(2)
199+
200+ width: highlightItem.width
201+ && contentWidth > highlightItem.width - userIcon.width - units.gu(4)
202+ ? highlightItem.width - userIcon.width - units.gu(4)
203+ : contentWidth
204+
205+ Component.onCompleted: _realName = realName
206+
207+ Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
208+ }
209+
210+ Row {
211+ spacing: units.gu(1)
212+
213+ FadingLabel {
214+ text: root.alphanumeric ? "Login with password" : "Login with pin"
215+ color: theme.palette.normal.raisedSecondaryText
216+ visible: userList.currentIndex === index && false
217+ font.weight: Font.Light
218+ font.pointSize: units.gu(1.25)
219+ }
220+ }
221+ }
192222 }
193223
194224 MouseArea {
@@ -197,7 +227,7 @@ StyledItem {
197227 right: parent.right
198228 top: parent.top
199229 // Add an offset to topMargin for any items below the highlight
200− topMargin: parent.belowHighlight ? parent.belowOffset : 0
230+ topMargin: parent.belowHighlight ? parent.belowOffset : parent.aboveCurrent ? -units.gu(5) : 0
201231 }
202232 height: parent.height
203233 enabled: userList.currentIndex !== index && parent.opacity > 0
@@ -217,67 +247,6 @@ StyledItem {
217247 }
218248 }
219249
220− // Use an AbstractButton due to icon limitations with Button
221− AbstractButton {
222− id: sessionChooser
223− objectName: "sessionChooserButton"
224−
225− readonly property alias icon: badge.source
226−
227− visible: LightDMService.sessions.count > 1 &&
228− !LightDMService.users.data(userList.currentIndex, LightDMService.userRoles.LoggedInRole)
229−
230− height: units.gu(3.5)
231− width: units.gu(3.5)
232−
233− activeFocusOnTab: true
234− anchors {
235− right: highlightItem.right
236− rightMargin: units.gu(2)
237−
238− top: highlightItem.top
239− topMargin: units.gu(1.5)
240− }
241−
242− Rectangle {
243− id: badgeHighlight
244−
245− anchors.fill: parent
246− visible: parent.activeFocus
247− color: "transparent"
248− border.color: theme.palette.normal.focus
249− border.width: units.dp(1)
250− radius: 3
251− }
252−
253− Icon {
254− id: badge
255− anchors.fill: parent
256− anchors.margins: units.dp(3)
257− keyColor: "#ffffff" // icon providers give us white icons
258− color: theme.palette.normal.raisedSecondaryText
259− source: LightDMService.sessions.iconUrl(root.currentSession)
260− }
261−
262− Keys.onReturnPressed: {
263− sessionChooserButtonClicked();
264− event.accepted = true;
265− }
266−
267− onClicked: {
268− sessionChooserButtonClicked();
269− }
270−
271− // Refresh the icon path if looking at different places at runtime
272− // this is mainly for testing
273− Connections {
274− target: LightDMService.sessions
275− onIconSearchDirectoriesChanged: {
276− badge.source = LightDMService.sessions.iconUrl(root.currentSession)
277− }
278− }
279− }
280−
281250 PromptList {
282251 id: promptList
283252 objectName: "promptList"
@@ -288,6 +257,8 @@ StyledItem {
288257 }
289258 width: highlightItem.width - anchors.margins * 2
290259
260+ focus: true
261+
291262 onClicked: {
292263 interactive = false;
293264 if (root.locked) {
qml/Greeter/NarrowView.qml+9−6
@@ -17,6 +17,7 @@
1717
1818import QtQuick 2.4
1919import QtQuick.Window 2.2
20+import QtGraphicalEffects 1.12
2021import Ubuntu.Components 1.3
2122import Ubuntu.Telephony 0.1 as Telephony
2223import "../Components"
@@ -34,6 +35,7 @@ FocusScope {
3435 property bool hasCustomBackground
3536 property bool locked
3637 property alias alphanumeric: loginList.alphanumeric
38+ property alias hasKeyboard: loginList.hasKeyboard
3739 property alias userModel: loginList.model
3840 property alias infographicModel: coverPage.infographicModel
3941 property string sessionToStart
@@ -42,8 +44,7 @@ FocusScope {
4244 readonly property bool required: coverPage.required || lockscreen.required
4345 readonly property bool animating: coverPage.showAnimation.running || coverPage.hideAnimation.running
4446
45− // so that it can be replaced in tests with a mock object
46− property var inputMethod: Qt.inputMethod
47+ property rect inputMethodRect
4748
4849 signal selected(int index)
4950 signal responded(string response)
@@ -90,6 +91,10 @@ FocusScope {
9091 }
9192 }
9293
94+ Keys.onSpacePressed: coverPage.hide();
95+ Keys.onReturnPressed: coverPage.hide();
96+ Keys.onEnterPressed: coverPage.hide();
97+
9398 Showable {
9499 id: lockscreen
95100 objectName: "lockscreen"
@@ -207,8 +212,7 @@ FocusScope {
207212 anchors.right: parent.right
208213 anchors.top: parent.bottom
209214 anchors.topMargin: - height * (1 - coverPage.showProgress)
210− - (inputMethod && inputMethod.visible ?
211− inputMethod.keyboardRectangle.height : 0)
215+ - ( inputMethodRect.height )
212216
213217 Rectangle {
214218 color: UbuntuColors.porcelain // matches OSK background
@@ -264,8 +268,7 @@ FocusScope {
264268 // during OSK animations.
265269 Rectangle {
266270 visible: bottomBar.visible
267− height: inputMethod && inputMethod.visible ?
268− inputMethod.keyboardRectangle.height : 0
271+ height: inputMethodRect.height
269272 anchors.bottom: parent.bottom
270273 anchors.left: parent.left
271274 anchors.right: parent.right
qml/Greeter/PinPrompt.qmlnew file+184
@@ -0,0 +1,184 @@
1+import QtQuick 2.4
2+import Ubuntu.Components 1.3
3+import "../Components"
4+
5+FocusScope {
6+ id: root
7+
8+ property string text
9+ property bool isSecret
10+ property bool interactive: true
11+ property bool loginError: false
12+ property bool hasKeyboard: false
13+ property alias enteredText: passwordInput.text
14+
15+ signal clicked()
16+ signal canceled()
17+ signal accepted(string response)
18+
19+ StyledItem {
20+ id: d
21+
22+ readonly property color textColor: passwordInput.enabled ? theme.palette.normal.raisedText
23+ : theme.palette.disabled.raisedText
24+ readonly property color selectedColor: passwordInput.enabled ? theme.palette.normal.raised
25+ : theme.palette.disabled.raised
26+ readonly property color drawColor: passwordInput.enabled ? theme.palette.normal.raisedSecondaryText
27+ : theme.palette.disabled.raisedSecondaryText
28+ readonly property color errorColor: passwordInput.enabled ? theme.palette.normal.negative
29+ : theme.palette.disabled.negative
30+ }
31+
32+ TextField {
33+ id: passwordInput
34+ objectName: "promptField"
35+ anchors.left: extraIcons.left
36+ focus: root.focus
37+
38+ opacity: fakeLabel.visible ? 0 : 1
39+ activeFocusOnTab: true
40+
41+ onSelectedTextChanged: passwordInput.deselect()
42+ onCursorPositionChanged: cursorPosition = length
43+
44+ validator: RegExpValidator {
45+ regExp: /^\d{4}$/
46+ }
47+
48+ inputMethodHints: Qt.ImhSensitiveData | Qt.ImhNoPredictiveText |
49+ Qt.ImhMultiLine | // so OSK doesn't close on Enter
50+ Qt.ImhDigitsOnly
51+ echoMode: TextInput.Password
52+ hasClearButton: false
53+
54+ cursorDelegate: Item {}
55+
56+ passwordCharacter: "●"
57+ color: d.drawColor
58+
59+ readonly property real letterSpacing: units.gu(1.75)
60+ readonly property real frameSpacing: letterSpacing
61+
62+ font.pixelSize: units.gu(3)
63+ font.letterSpacing: letterSpacing
64+
65+ style: StyledItem {
66+ anchors.fill: parent
67+ styleName: "FocusShape"
68+
69+ // Properties needed by TextField
70+ readonly property color color: d.textColor
71+ readonly property color selectedTextColor: d.selectedColor
72+ readonly property color selectionColor: d.textColor
73+ readonly property color borderColor: "transparent"
74+ readonly property color backgroundColor: "transparent"
75+ readonly property color errorColor: d.errorColor
76+ readonly property real frameSpacing: 0
77+
78+ // Properties needed by FocusShape
79+ readonly property bool enabled: styledItem.enabled
80+ readonly property bool keyNavigationFocus: styledItem.keyNavigationFocus
81+ property bool activeFocusOnTab
82+ }
83+
84+ onDisplayTextChanged: {
85+ // We use onDisplayTextChanged instead of onTextChanged because
86+ // displayText changes after text and if we did this before it
87+ // updated, we would use the wrong displayText for fakeLabel.
88+ root.loginError = false;
89+ if (text.length >= 4) {
90+ // hard limit of 4 for passcodes right now
91+ respond();
92+ }
93+ }
94+
95+ onAccepted: respond()
96+
97+ function respond() {
98+ if (root.interactive) {
99+ root.accepted(passwordInput.text);
100+ }
101+ }
102+
103+ Keys.onEscapePressed: {
104+ root.canceled();
105+ event.accepted = true;
106+ }
107+ }
108+
109+ Row {
110+ id: extraIcons
111+ spacing: passwordInput.frameSpacing
112+ anchors {
113+ horizontalCenter: parent ? parent.horizontalCenter : undefined
114+ horizontalCenterOffset: passwordInput.letterSpacing / 2
115+ verticalCenter: passwordInput ? passwordInput.verticalCenter : undefined
116+ }
117+
118+ Label {
119+ id: pinHint
120+ objectName: "promptPinHint"
121+
122+ text: "○○○○"
123+ enabled: visible
124+ color: d.drawColor
125+ font {
126+ pixelSize: units.gu(3)
127+ letterSpacing: units.gu(1.75)
128+ }
129+ elide: Text.ElideRight
130+ }
131+ Icon {
132+ name: "keyboard-caps-enabled"
133+ height: units.gu(3)
134+ width: height
135+ color: d.drawColor
136+ visible: false // TODO: detect when caps lock is on
137+ anchors.verticalCenter: parent.verticalCenter
138+ }
139+ Icon {
140+ objectName: "greeterPromptKeyboardButton"
141+ name: "input-keyboard-symbolic"
142+ height: units.gu(3)
143+ width: height
144+ color: d.drawColor
145+ visible: !unity8Settings.alwaysShowOsk && root.hasKeyboard
146+ anchors.verticalCenter: parent.verticalCenter
147+ MouseArea {
148+ anchors.fill: parent
149+ onClicked: unity8Settings.alwaysShowOsk = true
150+ }
151+ }
152+ Icon {
153+ name: "dialog-warning-symbolic"
154+ height: units.gu(3)
155+ width: height
156+ color: d.drawColor
157+ visible: root.loginError
158+ anchors.verticalCenter: parent.verticalCenter
159+ }
160+ }
161+
162+ // Have a fake label that covers the text field after the user presses
163+ // enter. What we *really* want is a disabled mode that doesn't lose OSK
164+ // focus. Because our goal here is simply to keep the OSK up while
165+ // we wait for PAM to get back to us, and while waiting, we don't want
166+ // the user to be able to edit the field (simply because it would look
167+ // weird if we allowed that). But until we have such a disabled mode,
168+ // we'll fake it by covering the real text field with a label.
169+ FadingLabel {
170+ id: fakeLabel
171+ anchors.verticalCenter: extraIcons ? extraIcons.verticalCenter : undefined
172+ anchors.left: extraIcons ? extraIcons.left : undefined
173+ anchors.right: parent ? parent.right : undefined
174+ anchors.rightMargin: passwordInput.frameSpacing * 2 + extraIcons.width
175+ color: d.drawColor
176+ font {
177+ pixelSize: pinHint.font.pixelSize
178+ letterSpacing: pinHint.font.letterSpacing
179+ }
180+ text: passwordInput.displayText
181+ visible: !root.interactive
182+ enabled: visible
183+ }
184+}
qml/Greeter/PromptList.qml+7−1
@@ -25,6 +25,8 @@ FocusScope {
2525
2626 property bool alphanumeric: true
2727 property bool interactive: true
28+ property bool loginError: false
29+ property bool hasKeyboard: false
2830
2931 signal responded(string text)
3032 signal clicked()
@@ -99,12 +101,14 @@ FocusScope {
99101 property var model
100102 readonly property bool isPrompt: false
101103
102− color: model.type === LightDMService.prompts.Message ? theme.palette.normal.raisedText
104+ color: model.type === LightDMService.prompts.Message ? theme.palette.normal.raisedSecondaryText
103105 : theme.palette.normal.negative
104106 fontSize: "small"
105107 textFormat: Text.PlainText
106108 text: model.text
107109
110+ visible: model.type === LightDMService.prompts.Message
111+
108112 Behavior on opacity { UbuntuNumberAnimation {} }
109113 opacity: 0
110114 }
@@ -123,6 +127,8 @@ FocusScope {
123127 isAlphanumeric: model.text !== "" || root.alphanumeric
124128 isPrompt: model.type !== LightDMService.prompts.Button
125129 isSecret: model.type === LightDMService.prompts.Secret
130+ loginError: root.loginError
131+ hasKeyboard: root.hasKeyboard
126132 text: model.text ? model.text : (isAlphanumeric ? i18n.tr("Passphrase") : i18n.tr("Passcode"))
127133
128134 onClicked: root.clicked()
qml/Greeter/SessionsList.qml+3−2
@@ -119,13 +119,14 @@ Item {
119119
120120 padding.leading: 0 // handled by parent's margins
121121
122− title.color: theme.palette.normal.raisedText
122+ title.color: theme.palette.normal.raisedSecondaryText
123123 title.font.pixelSize: units.gu(2.1)
124124 title.text: i18n.tr("Select desktop environment")
125125
126126 Icon {
127127 id: icon
128128 width: units.gu(3)
129+ color: theme.palette.normal.raisedSecondaryText
129130 SlotsLayout.position: SlotsLayout.Leading
130131 name: "go-previous"
131132
@@ -187,7 +188,7 @@ Item {
187188 ListItemLayout {
188189 id: layout
189190
190− readonly property color itemColor: theme.palette.normal.raisedText
191+ readonly property color itemColor: theme.palette.normal.raisedSecondaryText
191192 SessionIcon {
192193 id: sessionIcon
193194 source: icon_url
qml/Greeter/TextPrompt.qmlnew file+198
@@ -0,0 +1,198 @@
1+import QtQuick 2.4
2+import Ubuntu.Components 1.3
3+import "../Components"
4+
5+FocusScope {
6+ id: root
7+
8+ property string text
9+ property bool isSecret
10+ property bool interactive: true
11+ property bool loginError: false
12+ property bool hasKeyboard: false
13+ property alias enteredText: passwordInput.text
14+
15+ signal clicked()
16+ signal canceled()
17+ signal accepted(string response)
18+
19+ StyledItem {
20+ id: d
21+
22+ readonly property color textColor: passwordInput.enabled ? theme.palette.normal.raisedText
23+ : theme.palette.disabled.raisedText
24+ readonly property color selectedColor: passwordInput.enabled ? theme.palette.normal.raised
25+ : theme.palette.disabled.raised
26+ readonly property color drawColor: passwordInput.enabled ? theme.palette.normal.raisedSecondaryText
27+ : theme.palette.disabled.raisedSecondaryText
28+ readonly property color errorColor: passwordInput.enabled ? theme.palette.normal.negative
29+ : theme.palette.disabled.negative
30+ }
31+
32+ Rectangle {
33+ anchors.fill: parent
34+ radius: units.gu(0.5)
35+ color: "#7A111111"
36+ Behavior on border.color {
37+ ColorAnimation{}
38+ }
39+ border {
40+ color: root.loginError ? d.errorColor : d.drawColor
41+ width: root.loginError ? units.dp(2): units.dp(1)
42+ }
43+ }
44+
45+ TextField {
46+ id: passwordInput
47+ objectName: "promptField"
48+ anchors.fill: parent
49+ focus: root.focus
50+
51+ opacity: fakeLabel.visible ? 0 : 1
52+ activeFocusOnTab: true
53+
54+ onSelectedTextChanged: passwordInput.deselect()
55+
56+ validator: RegExpValidator {
57+ regExp: /^.*$/
58+ }
59+
60+ inputMethodHints: Qt.ImhSensitiveData | Qt.ImhNoPredictiveText |
61+ Qt.ImhMultiLine // so OSK doesn't close on Enter
62+ echoMode: root.isSecret ? TextInput.Password : TextInput.Normal
63+ hasClearButton: false
64+
65+ passwordCharacter: "●"
66+ color: d.drawColor
67+
68+ readonly property real frameSpacing: units.gu(1)
69+
70+ style: StyledItem {
71+ anchors.fill: parent
72+ styleName: "FocusShape"
73+
74+ // Properties needed by TextField
75+ readonly property color color: d.textColor
76+ readonly property color selectedTextColor: d.selectedColor
77+ readonly property color selectionColor: d.textColor
78+ readonly property color borderColor: "transparent"
79+ readonly property color backgroundColor: "transparent"
80+ readonly property color errorColor: d.errorColor
81+ readonly property real frameSpacing: styledItem.frameSpacing
82+
83+ // Properties needed by FocusShape
84+ readonly property bool enabled: styledItem.enabled
85+ readonly property bool keyNavigationFocus: styledItem.keyNavigationFocus
86+ property bool activeFocusOnTab
87+ }
88+
89+ secondaryItem: [
90+ Row {
91+ id: extraIcons
92+ spacing: passwordInput.frameSpacing
93+ anchors.verticalCenter: parent.verticalCenter
94+ Icon {
95+ name: "keyboard-caps-enabled"
96+ height: units.gu(3)
97+ width: units.gu(3)
98+ color: d.drawColor
99+ visible: root.isSecret && false // TODO: detect when caps lock is on
100+ anchors.verticalCenter: parent.verticalCenter
101+ }
102+ Icon {
103+ objectName: "greeterPromptKeyboardButton"
104+ name: "input-keyboard-symbolic"
105+ height: units.gu(3)
106+ width: units.gu(3)
107+ color: d.drawColor
108+ visible: !unity8Settings.alwaysShowOsk && root.hasKeyboard
109+ anchors.verticalCenter: parent.verticalCenter
110+ MouseArea {
111+ anchors.fill: parent
112+ onClicked: unity8Settings.alwaysShowOsk = true
113+ }
114+ }
115+ Icon {
116+ name: "dialog-warning-symbolic"
117+ height: units.gu(3)
118+ width: units.gu(3)
119+ color: d.drawColor
120+ visible: root.loginError
121+ anchors.verticalCenter: parent.verticalCenter
122+ }
123+ Icon {
124+ name: "toolkit_chevron-ltr_2gu"
125+ height: units.gu(2.5)
126+ width: units.gu(2.5)
127+ color: d.drawColor
128+ visible: !root.loginError
129+ anchors.verticalCenter: parent.verticalCenter
130+ MouseArea {
131+ anchors.fill: parent
132+ onClicked: root.accepted(passwordInput.text)
133+ }
134+ }
135+ }
136+ ]
137+
138+ onDisplayTextChanged: {
139+ // We use onDisplayTextChanged instead of onTextChanged because
140+ // displayText changes after text and if we did this before it
141+ // updated, we would use the wrong displayText for fakeLabel.
142+ root.loginError = false;
143+ }
144+
145+ onAccepted: respond()
146+
147+ function respond() {
148+ if (root.interactive) {
149+ root.accepted(passwordInput.text);
150+ }
151+ }
152+
153+ Keys.onEscapePressed: {
154+ root.canceled();
155+ event.accepted = true;
156+ }
157+ }
158+
159+ // We use our own custom placeholder label instead of the standard
160+ // TextField one because the standard one hardcodes baseText as the
161+ // palette color, whereas we want raisedSecondaryText.
162+ Label {
163+ id: passwordHint
164+ objectName: "promptHint"
165+ anchors {
166+ left: passwordInput ? passwordInput.left : undefined
167+ right: passwordInput ? passwordInput.right : undefined
168+ verticalCenter: passwordInput ? passwordInput.verticalCenter : undefined
169+ leftMargin: units.gu(2)
170+ rightMargin: anchors.leftMargin + extraIcons.width
171+ }
172+ text: root.text
173+ visible: passwordInput.text == "" && !passwordInput.inputMethodComposing
174+ enabled: visible
175+ color: d.drawColor
176+ elide: Text.ElideRight
177+ }
178+
179+ // Have a fake label that covers the text field after the user presses
180+ // enter. What we *really* want is a disabled mode that doesn't lose OSK
181+ // focus. Because our goal here is simply to keep the OSK up while
182+ // we wait for PAM to get back to us, and while waiting, we don't want
183+ // the user to be able to edit the field (simply because it would look
184+ // weird if we allowed that). But until we have such a disabled mode,
185+ // we'll fake it by covering the real text field with a label.
186+ FadingLabel {
187+ id: fakeLabel
188+ anchors.verticalCenter: parent ? parent.verticalCenter : undefined
189+ anchors.left: parent ? parent.left : undefined
190+ anchors.right: parent ? parent.right : undefined
191+ anchors.leftMargin: passwordInput.frameSpacing * 2
192+ anchors.rightMargin: passwordInput.frameSpacing * 2 + extraIcons.width
193+ color: d.drawColor
194+ text: passwordInput.displayText
195+ visible: !root.interactive
196+ enabled: visible
197+ }
198+}
qml/Greeter/WideView.qml+75−4
@@ -35,6 +35,7 @@ FocusScope {
3535 property alias currentIndex: loginList.currentIndex
3636 property int delayMinutes // TODO
3737 property alias alphanumeric: loginList.alphanumeric
38+ property alias hasKeyboard: loginList.hasKeyboard
3839 property alias locked: loginList.locked
3940 property alias waiting: loginList.waiting
4041 property var userModel // Set from outside
@@ -44,8 +45,7 @@ FocusScope {
4445 readonly property bool required: coverPage.required
4546 readonly property alias sessionToStart: loginList.currentSession
4647
47− // so that it can be replaced in tests with a mock object
48− property var inputMethod: Qt.inputMethod
48+ property rect inputMethodRect
4949
5050 signal selected(int index)
5151 signal responded(string response)
@@ -97,6 +97,18 @@ FocusScope {
9797 width: parent.width
9898 draggable: !root.locked && !root.waiting
9999 state: "LoginList"
100+ blurAreaHeight: loginList.highlightedHeight + units.gu(4.5)
101+ blurAreaWidth: loginList.width + units.gu(3)
102+ blurAreaX: loginList.x - units.gu(1.5)
103+ blurAreaY: loginList.boxVerticalOffset + loginList.y - units.gu(3)
104+
105+ // Darken background to match CoverPage
106+ Rectangle {
107+ objectName: "lockscreenShade"
108+ anchors.fill: parent
109+ color: "black"
110+ opacity: root.hasCustomBackground ? 0.1 : 0
111+ }
100112
101113 infographics {
102114 anchors.topMargin: parent.height * 0.125
@@ -125,8 +137,7 @@ FocusScope {
125137 }
126138
127139 boxVerticalOffset: (height - highlightedHeight -
128− (inputMethod && inputMethod.visible ?
129− inputMethod.keyboardRectangle.height : 0)) / 2
140+ inputMethodRect.height) / 2
130141 Behavior on boxVerticalOffset { UbuntuNumberAnimation {} }
131142
132143 model: root.userModel
@@ -175,6 +186,66 @@ FocusScope {
175186 }
176187 }
177188
189+ // Use an AbstractButton due to icon limitations with Button
190+ AbstractButton {
191+ id: sessionChooser
192+ objectName: "sessionChooserButton"
193+
194+ readonly property url icon: LightDMService.sessions.iconUrl(loginList.currentSession)
195+
196+ visible: LightDMService.sessions.count > 1 &&
197+ !LightDMService.users.data(loginList.currentUserIndex, LightDMService.userRoles.LoggedInRole)
198+
199+ height: units.gu(3.5)
200+ width: units.gu(3.5)
201+
202+ activeFocusOnTab: true
203+ anchors {
204+ right: parent.right
205+ rightMargin: units.gu(2)
206+
207+ bottom: parent.bottom
208+ bottomMargin: units.gu(1.5)
209+ }
210+
211+ Rectangle {
212+ id: badgeHighlight
213+
214+ anchors.fill: parent
215+ visible: parent.activeFocus
216+ color: "transparent"
217+ border.color: theme.palette.normal.focus
218+ border.width: units.dp(1)
219+ radius: 3
220+ }
221+
222+ Icon {
223+ id: badge
224+ anchors.fill: parent
225+ anchors.margins: units.dp(3)
226+ keyColor: "#ffffff" // icon providers give us white icons
227+ color: theme.palette.normal.raisedSecondaryText
228+ source: sessionChooser.icon
229+ }
230+
231+ Keys.onReturnPressed: {
232+ parent.state = "SessionsList";
233+ }
234+
235+ onClicked: {
236+ parent.state = "SessionsList";
237+ }
238+
239+ // Refresh the icon path if looking at different places at runtime
240+ // this is mainly for testing
241+ Connections {
242+ target: LightDMService.sessions
243+ onIconSearchDirectoriesChanged: {
244+ badge.source = LightDMService.sessions.iconUrl(root.currentSession)
245+ }
246+ }
247+ }
248+
178249 states: [
179250 State {
180251 name: "SessionsList"
qml/Shell.qml+3−1
@@ -1,4 +1,4 @@
1−/*
1+/*
22 * Copyright (C) 2013-2016 Canonical, Ltd.
33 * Copyright (C) 2019-2021 UBports Foundation
44 *
@@ -440,6 +440,8 @@ StyledItem {
440440 background: wallpaperResolver.background
441441 backgroundSourceSize: shell.largestScreenDimension
442442 hasCustomBackground: wallpaperResolver.hasCustomBackground
443+ inputMethodRect: inputMethod.visibleRect
444+ hasKeyboard: shell.hasKeyboard
443445 allowFingerprint: !dialogs.hasActiveDialog &&
444446 !notifications.topmostIsFullscreen &&
445447 !panel.indicators.shown
tests/qmltests/Greeter/tst_WideView.qml+37−17
@@ -55,7 +55,8 @@ StyledItem {
5555 delayMinutes: parseInt(delayMinutesField.text, 10)
5656 panelHeight: parseFloat(panelHeightField.text)
5757 locked: lockedCheckBox.checked
58− inputMethod: fakeInputMethod
58+ inputMethodRect: fakeKeyboard.childrenRect
59+ property var testKeyboard: fakeKeyboard
5960
6061 Component.onDestruction: {
6162 loader.itemDestroyed = true
@@ -66,24 +67,13 @@ StyledItem {
6667 currentIndexField.text = index;
6768 }
6869
69− QtObject {
70− id: fakeInputMethod
71− property bool visible: fakeKeyboard.visible
72− property var keyboardRectangle: QtObject {
73− property real x: fakeKeyboard.x
74− property real y: fakeKeyboard.y
75− property real width: fakeKeyboard.width
76− property real height: fakeKeyboard.height
77− }
78− }
79−
8070 Rectangle {
8171 id: fakeKeyboard
8272 color: "green"
8373 opacity: 0.7
8474 anchors.bottom: view.bottom
8575 width: view.width
86− height: view.height * 0.6
76+ height: visible ? view.height * 0.6 : 0
8777 visible: keyboardVisibleCheckBox.checked
8878 Text {
8979 text: "Keyboard Rectangle"
@@ -672,13 +662,13 @@ StyledItem {
672662 keyboardVisibleCheckBox.checked = true;
673663
674664 var halfway = (view.height - loginList.highlightedHeight) / 2;
675− var halfwayWithOsk = halfway - view.inputMethod.keyboardRectangle.height / 2;
665+ var halfwayWithOsk = halfway - view.inputMethodRect.height / 2;
676666 tryCompare(loginList, "boxVerticalOffset", halfwayWithOsk);
677667
678668 var highlightItem = findChild(loginList, "highlightItem");
679669 tryCompareFunction( function() {
680670 var highlightRect = highlightItem.mapToItem(view, 0, 0, highlightItem.width, highlightItem.height);
681− return highlightRect.y + highlightRect.height <= view.inputMethod.keyboardRectangle.y;
671+ return highlightRect.y + highlightRect.height <= view.testKeyboard.y;
682672 }, true);
683673
684674 // once the vkb goes away, loginList goes back to its full height
@@ -701,9 +691,9 @@ StyledItem {
701691
702692 function test_passcode() {
703693 var index = selectUser("has-pin");
704− var promptField = findChild(view, "promptField");
705−
706694 view.alphanumeric = false;
695+
696+ var promptField = findChild(view, "promptField");
707697 compare(promptField.inputMethodHints & Qt.ImhDigitsOnly, Qt.ImhDigitsOnly);
708698
709699 keyClick(Qt.Key_D);
@@ -777,5 +767,35 @@ StyledItem {
777767 var greeterPrompt = findChild(view, "greeterPrompt0");
778768 verify(greeterPrompt.activeFocus);
779769 }
770+
771+ function test_scrollChangesIndex() {
772+ /* Check if the index of the current user gets changed
773+ * if we scroll through the list.
774+ * Expected result: the list has moved at least by one
775+ * user and the index of the user is reported using the
776+ * selected signal in LoginList.qml
777+ * https://github.com/ubports/unity8/issues/397
778+ */
779+ var loginList = findChild(view, "loginList");
780+ // FIXME: Fix scrolling sensitivity (scrolling by -1 shouldn't move more than one user down)
781+ mouseWheel( loginList, loginList.width / 2, loginList.height / 2, 0, -1, null );
782+ selectedSpy.wait();
783+ tryVerify(function(){ return selectedSpy.signalArguments[0][0] > 0 });
784+ }
785+
786+ function test_dragChangesIndex() {
787+ /* Check if the index of the current user gets changed
788+ * if we drag the list or swipe.on it.
789+ * Expected result: the list has moved at least by one
790+ * user and the index of the user is reported using the
791+ * selected signal in LoginList.qml
792+ * https://github.com/ubports/unity8/issues/397
793+ */
794+ var loginList = findChild(view, "loginList");
795+ // FIXME: Fix scrolling sensitivity (the number of scrolled users is randomly changing)
796+ touchFlick(loginList, loginList.width/2, loginList.height/3, loginList.width/2, loginList.height/3 -units.gu(2.1));
797+ selectedSpy.wait();
798+ tryVerify(function(){ return selectedSpy.signalArguments[0][0] > 0 });
799+ }
780800 }
781801}
tests/qmltests/tst_OrientedShell.qml+44−3
@@ -40,11 +40,12 @@ Rectangle {
4040 height: units.gu(100)
4141
4242 property var tryShell: null
43+ property string ldmUserMode: "single"
4344
4445 Binding {
4546 target: LightDMController
4647 property: "userMode"
47− value: "single"
48+ value: ldmUserMode
4849 }
4950
5051 QtObject {
@@ -240,6 +241,23 @@ Rectangle {
240241 LightDM.Greeter.showGreeter();
241242 }
242243 }
244+
245+
246+ Label {
247+ text: "LightDM mock mode"
248+ }
249+
250+ ListItem.ItemSelector {
251+ anchors { left: parent.left; right: parent.right }
252+ activeFocusOnPress: false
253+ model: ["single", "single-passphrase", "single-pin", "full"]
254+ onSelectedIndexChanged: {
255+ testCase.tearDown();
256+ ldmUserMode = model[selectedIndex];
257+ testCase.init();
258+ }
259+ }
260+
243261 Label {
244262 text: "Physical Orientation:"
245263 }
@@ -1482,7 +1500,8 @@ Rectangle {
14821500 touchFlick(shell, touchX, touchY, orientedShell.width * 0.1, touchY);
14831501
14841502 // wait until the animation has finished
1485− tryCompare(greeter, "shown", false);
1503+ if (LightDMController.userMode == "single")
1504+ tryCompare(greeter, "shown", false);
14861505 waitForRendering(greeter);
14871506 }
14881507
@@ -1493,9 +1512,11 @@ Rectangle {
14931512 tryCompare(greeter, "fullyShown", true);
14941513 }
14951514
1496− function loadShell(deviceName) {
1515+ function loadShell(deviceName, userMode = "single") {
14971516 applicationArguments.deviceName = deviceName;
14981517
1518+ ldmUserMode = userMode; // Set the mode for LightDM ( default is "single" )
1519+
14991520 // reload our test subject to get it in a fresh state once again
15001521 var orientedShell = createTemporaryObject(shellComponent, shellRect);
15011522
@@ -1697,5 +1718,25 @@ Rectangle {
16971718 MockInputDeviceBackend.removeDevice("/touchscreen");
16981719 tryCompare(tutorial, "paused", true);
16991720 }
1721+
1722+ /* Check if the keyboard icon on the greeter screen
1723+ * is only shown when an external keyboard is attached
1724+ * and if it is hidden when no keyboard is attached.
1725+ */
1726+ function test_greeterKeyboardDetection() {
1727+ var orientedShell = loadShell("mako", "single-passphrase");
1728+ MockInputDeviceBackend.removeDevice("/indicator_kbd0");
1729+
1730+ var greeterPrompt = findChild(orientedShell, "greeterPrompt0");
1731+ verify(greeterPrompt);
1732+
1733+ var promptKeyboard = findChild(greeterPrompt, "greeterPromptKeyboardButton");
1734+
1735+ tryCompare(promptKeyboard, "visible", false);
1736+
1737+ MockInputDeviceBackend.addMockDevice("/kbd0", InputInfo.Keyboard);
1738+
1739+ tryCompare(promptKeyboard, "visible", true);
1740+ }
17001741 }
17011742}
tests/qmltests/tst_Shell.qml+10−7
@@ -763,13 +763,16 @@ Rectangle {
763763 var greeter = findChild(shell, "greeter")
764764 tryCompare(greeter, "fullyShown", true);
765765
766− var promptButton = findChild(greeter, "promptButton");
767− tryCompare(promptButton, "visible", isButton);
768−
769− var promptField = findChild(greeter, "promptField");
770− tryCompare(promptField, "visible", !isButton);
771−
772− mouseClick(promptButton);
766+ if (isButton) {
767+ var promptButton = findChild(greeter, "promptButton");
768+ verify(promptButton);
769+ tryCompare(promptButton, "visible", true);
770+ mouseClick(promptButton);
771+ } else {
772+ var promptField = findChild(greeter, "promptField");
773+ verify(promptField);
774+ tryCompare(promptField, "visible", true);
775+ }
773776 }
774777
775778 function confirmLoggedIn(loggedIn) {