Merge lp:~mterry/unity8/greeter-refactor into lp:unity8

Proposed by Michael Terry
Status: Merged
Approved by: Daniel d'Andrada
Approved revision: 1507
Merged at revision: 1622
Proposed branch: lp:~mterry/unity8/greeter-refactor
Merge into: lp:unity8
Prerequisite: lp:~mterry/unity8/tutorial-new-screens
Diff against target: 5614 lines (+2954/-1668)
28 files modified
cmake/modules/QmlTest.cmake (+1/-1)
qml/Components/DelayedLockscreen.qml (+3/-1)
qml/Components/PassphraseLockscreen.qml (+2/-3)
qml/Components/PinLockscreen.qml (+3/-4)
qml/Greeter/CoverPage.qml (+163/-110)
qml/Greeter/Greeter.qml (+358/-186)
qml/Greeter/Infographics.qml (+1/-0)
qml/Greeter/LoginList.qml (+70/-88)
qml/Greeter/NarrowView.qml (+170/-0)
qml/Greeter/WideView.qml (+134/-0)
qml/Shell.qml (+78/-364)
tests/autopilot/unity8/shell/emulators/greeter.py (+19/-3)
tests/autopilot/unity8/shell/emulators/main_window.py (+0/-6)
tests/autopilot/unity8/shell/tests/__init__.py (+2/-3)
tests/autopilot/unity8/shell/tests/test_lock_screen.py (+8/-12)
tests/plugins/LightDM/CMakeLists.txt (+21/-6)
tests/plugins/LightDM/usersmodel.cpp (+84/-0)
tests/qmltests/CMakeLists.txt (+4/-3)
tests/qmltests/Greeter/TestView.qml (+99/-0)
tests/qmltests/Greeter/tst_Greeter.qml (+472/-0)
tests/qmltests/Greeter/tst_MultiGreeter.qml (+0/-475)
tests/qmltests/Greeter/tst_NarrowView.qml (+522/-0)
tests/qmltests/Greeter/tst_SingleGreeter.qml (+0/-244)
tests/qmltests/Greeter/tst_WideView.qml (+519/-0)
tests/qmltests/Tutorial/tst_Tutorial.qml (+5/-4)
tests/qmltests/tst_Shell.qml (+65/-43)
tests/qmltests/tst_ShellWithPin.qml (+97/-48)
tests/qmltests/tst_TabletShell.qml (+54/-64)
To merge this branch: bzr merge lp:~mterry/unity8/greeter-refactor
Reviewer Review Type Date Requested Status
Michael Zanetti (community) Approve
Daniel d'Andrada (community) Approve
PS Jenkins bot (community) continuous-integration Needs Fixing
Review via email: mp+248829@code.launchpad.net

This proposal supersedes a proposal from 2015-01-29.

Commit message

Refactor the greeter code to be more compartmented and isolatable.

This will help make it an optional piece of the shell when we allow a split greeter/shell.

My driving goals were:

- Remove as much logic from Shell.qml as possible. Right now, there is so much Greeter logic spread all throughout Shell.qml in different bits. For example, the Lockscreen widget sitting there instead of in qml/Greeter/.

- Clean up how we load the two different views (tablet & phone). Right now we have some widgets always present but sometimes hidden (Lockscreen) and Loaders within Loaders (LoginList inside GreeterContent). I wanted just two top-level view widgets that could be separately loaded depending on which mode we are in.

- Toward that end, I wanted to consolidate all the Greeter logic out of Shell and out of those two view classes to avoid duplication. So I made the main Greeter entry point widget handle all the login logic and interactions with LightDM. It controls the views, who always ask it to do things on their behalf. Classic MVC stuff.

- I separated out some of the widgetry from the previous Greeter class into a CoverPage class that both NarrowView and WideView use. This class now holds the drag handle for sliding the greeter away and the infographic.

- Now that each piece of the greeter has a much more brightly defined boundary, testing them in isolation is much easier. I've added tst_WideView, tst_NarrowView, and tst_Greeter files. I've moved existing tests around a bit and added some more. But it should be much more clear where a prospective test belongs now (logic tests in Greeter, visual tests in the respective View).

- I also moved some tests we had in qmluitests/ that were purely testing the LightDM plugin into their own plugin test file.

Description of the change

* Are there any related MPs required for this MP to build/function as expected? Please list.
No.

* If you changed the packaging (debian), did you subscribe the ubuntu-unity team to this MP?
Not applicable.

* If you changed the UI, has there been a design review?
Not applicable.

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

I'm going to submit for review, but there is a tiny issue still with the OSK and the NarrowView passphrase interface. The OSK doesn't come back up automatically after an invalid passphrase, and the OSK stays up after a suspend.

So there is some focus issue there I'll look into. But it's good enough to review.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

About the commit message: Mind doing the "short summary on first line, empty line, rest of the explanation" format?

Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

"""
1174 + * Copyright (C) 2014 Canonical, Ltd.
"""

We're past that year, man. :)

Revision history for this message
Michael Terry (mterry) wrote : Posted in a previous version of this proposal

OK, copied "Description" into "Commit Message" and updated years. :)

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote : Posted in a previous version of this proposal

Text conflict in qml/Greeter/CoverPage.qml
Text conflict in qml/Greeter/Greeter.qml
Text conflict in qml/Shell.qml
Text conflict in tests/plugins/LightDM/CMakeLists.txt
Text conflict in tests/qmltests/CMakeLists.txt
Contents conflict in tests/qmltests/Greeter/tst_MultiGreeter.qml
Text conflict in tests/qmltests/tst_TabletShell.qml
7 conflicts encountered.

Revision history for this message
Michael Terry (mterry) wrote :

I rebased this MP on tutorial-new-screens / trunk, hopefully fixing all current conflicts.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~mterry/unity8/greeter-refactor updated
1485. By Michael Terry

Fix tutorial autopilot test with new greeter refactor

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~mterry/unity8/greeter-refactor updated
1486. By Michael Terry

Fix autopilot harder

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~mterry/unity8/greeter-refactor updated
1487. By Michael Terry

Wait until userlist settles at beginning of TabletShell test

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

There seems to be a badly resolved merge conflict:
qml/Components/EdgeDemo.qml got renamed to qml/Components/EdgeDemo.qml.THIS

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

Please update the copyright year of qml/Components/PassphraseLockscreen.qml

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

I run "make tryGreeter" and got a black screen on the left side. clicking the "show greeter" button has no visible effect. Is that expected?

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

"""
=== modified file 'qml/Components/PassphraseLockscreen.qml'
--- qml/Components/PassphraseLockscreen.qml 2014-10-23 13:23:07 +0000
+++ qml/Components/PassphraseLockscreen.qml 2015-02-02 22:14:36 +0000
@@ -98,6 +98,11 @@ Item {
                 // has oddly sized password characters that don't scale right)
                 opacity: 0

+ onFocusChanged: {
+ // Within our FocusScope, we always want to have focus
+ focus = true;
+ }
+
                 // simulate being disabled, but without removing OSK focus
                 maximumLength: root.entryEnabled ? 32767 : length

"""

This looks like a desperate hack. Setting "focus: true" should suffice (along with a FocusScope surrounding the offender in the worst case, if that makes sense). Do you know who is taking away the focus from this item?

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~mterry/unity8/greeter-refactor updated
1488. By Michael Terry

Small cleanups

1489. By Michael Terry

Make fake TestView more obviously intentionally-blank

Revision history for this message
Michael Terry (mterry) wrote :

> There seems to be a badly resolved merge conflict:
> qml/Components/EdgeDemo.qml got renamed to qml/Components/EdgeDemo.qml.THIS

Fixed, whoops!

> Please update the copyright year of qml/Components/PassphraseLockscreen.qml

Fixed.

> I run "make tryGreeter" and got a black screen on the left side.
> clicking the "show greeter" button has no visible effect. Is that expected?

Yes. tst_Greeter.qml only tests the "controller" side of things -- the glue between the user visible views and AccountsService/LightDM/PAM. It has its own View class, but it's just a fake one without any real controls. I've removed the "Show Greeter" button, since that was just a cut/paste artifact. And I've added a label to the fake view class to make it clear that there is nothing to see. "This page intentionally left blank" and all. :)

If you want to play with a view class, use tryWideView and tryNarrowView. If you want to play with the Greeter + a view, use tryShell, tryShellWithPin, or tryTabletShell.

> This looks like a desperate hack. Setting "focus: true" should suffice

Yeah... You got me. It didn't suffice, and I couldn't tell why focus was being stolen. All I could tell was that activeFocus was moving to the greeter's loader itself. But not in a situation that seemingly would require it to move (e.g. it wasn't being reloaded nor was a widget disabled as far as I could tell).

Revision history for this message
Michael Terry (mterry) wrote :

(Well, I guess it's not surprising that the loader got activeFocus, I think that was just the side effect of the entry's focus being false. What was surprising was that the entry object's focus property was being set to false in the first place. Wasn't sure why.)

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~mterry/unity8/greeter-refactor updated
1490. By Michael Terry

Restore behavior animating LoginList height

1491. By Michael Terry

Merge from tutorial-new-screens

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

"""
        Connections {
            target: ApplicationManager

            onFocusRequested: {
                [...]
                greeter.notifyAppFocused(appId);
            }

            onFocusedApplicationIdChanged: {
                greeter.notifyAppFocused(ApplicationManager.focusedApplicationId);
                [...]
            }

            onApplicationAdded: {
                [...]
                greeter.notifyAppFocused(appId);
                [...]
            }
        }
"""

All those greeter.notifyAppFocused calls look redundant and not really correct.

A focus request, as it name says, it's just a request. It will be processed by the window manager part of unity8 (eg PhoneStage or TabletStage) which will finally change the app that is focused, causing ApplicationManager.focusedApplicationId to change. Thus for every focus change request you will be calling greeter.notifyAppFocused twice. And what's worse, you're assuming that a request will always be successful and are telling greeter that a given app now has focus before it actually happens.

The same goes for the onApplicationAdded case.

Can't we have greeter.notifyAppFocused only in onFocusedApplicationIdChanged?

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

>
> A focus request, as it name says, it's just a request. It will be processed by
> the window manager part of unity8 (eg PhoneStage or TabletStage) which will
> finally change the app that is focused, causing
> ApplicationManager.focusedApplicationId to change. Thus for every focus change
> request you will be calling greeter.notifyAppFocused twice. And what's worse,
> you're assuming that a request will always be successful and are telling
> greeter that a given app now has focus before it actually happens.

Unless you wanna do something also when ApplicationManager.requestFocusApplication() is called for an application that is already focused? In which case we would need some further refactoring as the current naming would be misleading.

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

In Shell.qml:

"""
                available: tutorial.panelEnabled && (!greeter.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
"""

This line is way too long.

lp:~mterry/unity8/greeter-refactor updated
1492. By Michael Terry

Shorten a couple long lines

1493. By Michael Terry

Consolidate app-switching logic

1494. By Michael Terry

Add test and comment for the relied-upon behavior of emitting a focus id changed signal even when we focus the same app

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

run "make tryShell"

Start a left edge drag, up to 1/3 of the display width (to that you have the greeter move), then release it.

Expected outcome:
Greeter slides back into place, in an animated fashion

Actual outcome:
Greeter jumps immediately back into place. There's not animation at all.

review: Needs Fixing
Revision history for this message
Michael Terry (mterry) wrote :

> This line is way too long.

Fixed, plus another similar line.

> All those greeter.notifyAppFocused calls look redundant and not really correct.

Well, you are right. Back when I originally added "emergency dialer" support, I tested and the focusChanged signal was not emitted in all cases (like if it was already focused) and not everything went through the requestFocus path (some things were just focused without a request, by the shell). So I had redundant bits in each signal handler.

But not 100% redundant. Each behaved slightly different. In this branch, I've consolidated all that down to one function call per signal handler, and I felt pretty good about that. But you're right, the whole thing looks silly from someone that isn't in the weeds of it.

So stepping back, I retested. And nowadays, we seem to always properly requestFocus, not to mention that a focusChanged signal is emitted in all cases now. So all those signal handlers can consolidate down to one, when focusChanged is received.

So I've done that. Thanks for the kick in the pants! :) I've also added a test to cover the specific case of a re-focus on an already focused app from the greeter. We didn't test that precise case before, but since that one depends on special behavior of re-emitting a changed signal even when nothing changed, a test & comment seemed prudent.

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

> run "make tryShell"
>
> Start a left edge drag, up to 1/3 of the display width (to that you have the
> greeter move), then release it.
>
> Expected outcome:
> Greeter slides back into place, in an animated fashion
>
> Actual outcome:
> Greeter jumps immediately back into place. There's not animation at all.

Wonder if we could have a qml regression test for that, that checks that the greeter.x is changed to a couple of intermediate values before finally reaching zero.

lp:~mterry/unity8/greeter-refactor updated
1495. By Michael Terry

Animate letting go of the coverPage

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

1- make tryShellWithPin
2- swipe away the cover page
3- press "Show greeter" button

You get those warnings:
"""
qml/Components/PinLockscreen.qml:24: TypeError: Cannot read property 'top' of null
qml/Components/PinLockscreen.qml:26: TypeError: Cannot read property 'horizontalCenter' of null
"""

review: Needs Fixing
Revision history for this message
Michael Terry (mterry) wrote :

The animation when letting go of the coverPage while making a left drag is fixed too.

Revision history for this message
Michael Terry (mterry) wrote :

Oh, let me see about a test...

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

> 1- make tryShellWithPin
> 2- swipe away the cover page
> 3- press "Show greeter" button
>
> You get those warnings:
> """
> qml/Components/PinLockscreen.qml:24: TypeError: Cannot read property 'top' of
> null
> qml/Components/PinLockscreen.qml:26: TypeError: Cannot read property
> 'horizontalCenter' of null
> """

It also happens in trunk, but it would be good to have it fixed here

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

1- run make tryShell

A bunch of new warnings show up:
"""
qml/Greeter/Infographics.qml:39:5: QML Connections: Cannot assign to non-existent property "onDataDisappeared"
qml/Greeter/Infographics.qml:39:5: QML Connections: Cannot assign to non-existent property "onDataAboutToDisappear"
qml/Greeter/Infographics.qml:39:5: QML Connections: Cannot assign to non-existent property "onDataChanged"
qml/Greeter/Infographics.qml:39:5: QML Connections: Cannot assign to non-existent property "onDataAboutToChange"
qml/Greeter/Infographics.qml:39:5: QML Connections: Cannot assign to non-existent property "onDataAppeared"
qml/Greeter/Infographics.qml:39:5: QML Connections: Cannot assign to non-existent property "onDataAboutToAppear"
"""

then when you swipe away the greeter and press the "show greeter" button, you get all the warnings above plus this one:

"""
qml/Components/PassphraseLockscreen.qml:23: TypeError: Cannot read property 'top' of null
"""

review: Needs Fixing
lp:~mterry/unity8/greeter-refactor updated
1496. By Michael Terry

Stop some warnings from being printed

Revision history for this message
Michael Terry (mterry) wrote :

OK, warnings fixed.

lp:~mterry/unity8/greeter-refactor updated
1497. By Michael Terry

Add tiny test to confirm that we don't just jump to launcherOffset = 0

Revision history for this message
Michael Terry (mterry) wrote :

And a test added for the animation of the coverPage being let go.

Revision history for this message
Albert Astals Cid (aacid) wrote :

Text conflict in qml/Greeter/CoverPage.qml
1 conflicts encountered.

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

This launcherOffsetProxy thing in Greeter.qml is rather messy. Have you tried using a SmoothedAnimation, like that? http://paste.ubuntu.com/10189227/

Seems to work fine.

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

On 10/02/15 18:16, Michael Terry wrote:
>> This looks like a desperate hack. Setting "focus: true" should suffice
> Yeah... You got me. It didn't suffice, and I couldn't tell why focus was being stolen. All I could tell was that activeFocus was moving to the greeter's loader itself. But not in a situation that seemingly would require it to move (e.g. it wasn't being reloaded nor was a widget disabled as far as I could tell).

The latest unity8 now has a tool to help you find out who's stealing
your focus. See tests/uqmlscene/ActiveFocusLogger.cpp

You can use it to debug focus with "make tryFoo" by changing "#define
UQMLSCENE_DEBUG_ACTIVE_FOCUS 0" to 1 in tests/uqmlscene/main.cpp.

If you can only reproduce the issue on the device then you will have to
use ActiveFocusLogger in src/main.cpp. Just see how it's done in
tests/uqmlscene/main.cpp.

This gotta be clarified and fixed.

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

> This launcherOffsetProxy thing in Greeter.qml is rather messy. Have you tried
> using a SmoothedAnimation, like that? http://paste.ubuntu.com/10189227/
>
> Seems to work fine.

This seems perfect: http://paste.ubuntu.com/10189619/

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~mterry/unity8/greeter-refactor updated
1498. By Michael Terry

Merge from trunk

Revision history for this message
Michael Terry (mterry) wrote :

> This seems perfect: http://paste.ubuntu.com/10189619/

As I said on IRC:

"""
We don't want [the Behavior animation] when the user is driving the abrupt changes
Then we want to be immediately responsive
It makes the phone look laggy if we trail their finger
[Your latest patch is] still a noticable delay when jittering the mouse/finger. It's better for sure. But still noticable.
It feels clear to me that rather than speeding up a behavior so fast as to not be noticable in that case, we just want to disable it...
I understand it's more code, but it feels more correct
"""

So I'm still pushing back on this request. I'm happy to bow to more pressure, if we want a third opinion.

I am looking into the focus issue.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~mterry/unity8/greeter-refactor updated
1499. By Michael Terry

Add unused tst_ShellWithPassphrase.qml file, to allow tryShellWithPassphrase

1500. By Michael Terry

Remove (unneeded?) focus hack

Revision history for this message
Michael Terry (mterry) wrote :

Uh, so I guess the focus issue just doesn't exist anymore? Removing that code seems to have no effect. I can't reproduce the bug I was trying to fix (which occurred when entering a bad passphrase).

So I've taken the hack out entirely.

I've also added a bare-bones tst_ShellWithPassphrase.qml to allow testing the passphrase shell, which I just realized is hard to do without the benefit of "./run.sh -k" now.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

> I've also added a bare-bones tst_ShellWithPassphrase.qml to allow testing the
> passphrase shell, which I just realized is hard to do without the benefit of
> "./run.sh -k" now.

Now that the lightdm mock libs are unified we no longer need separate test files for different lightdm mock modes.

We can have them all under tst_Shell, like this:
http://paste.ubuntu.com/10202880/

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

I'm getting some test failures:

FAIL! : qmltestrunner::Shell::test_leftEdgeDrag(without launcher) Compared values are not the same
   Actual ():
   Expected (): visible
   Loc: [/home/dandrader/unity8/greeter-refactor/tests/qmltests/tst_Shell.qml(319)]

FAIL! : qmltestrunner::Shell::test_leftEdgeDrag(with launcher) Compared values are not the same
   Actual ():
   Expected (): visible
   Loc: [/home/dandrader/unity8/greeter-refactor/tests/qmltests/tst_Shell.qml(319)]

FAIL! : qmltestrunner::Shell::test_leftEdgeDrag(long swipe) Compared values are not the same
   Actual ():
   Expected (): visible
   Loc: [/home/dandrader/unity8/greeter-refactor/tests/qmltests/tst_Shell.qml(319)]

FAIL! : qmltestrunner::Shell::test_leftEdgeDrag(long swipe) property x
   Actual (): -64
   Expected (): 0
   Loc: [/home/dandrader/unity8/greeter-refactor/tests/qmltests/tst_Shell.qml(315)]

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

In Greeter.qml:

"""
   loader.item.authenticated(false);
"""

Please name it like a function, instead of like a boolean property.

I would suggest:
onAuthenticationSucceeded() and onAuthenticationFailed() (or replace the "on" prefix with "nofify", as it's the pattern you seem to be using already) as dropping the boolean parameter makes Greeter.qml code more readable. After all you're not saving any work with this boolean parameter as the implementation does "if(booleanParam){doSuccessfulPath}else{doFailurePath}" anyway.

eg:

"""
    function authenticated(success) {
        if (success) {
            lockscreen.hide();
        } else {
            lockscreen.clear(true);
        }
    }
"""

Would be:

"""
    function onAuthenticationSucceeded() { lockscreen.hide(); }
    function onAuthenticationFailed() { lockscreen.clear(true); }
"""

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

You have this both in Greeter.qml and CoverPage.qml:

"""
 * Copyright (C) 2013 Canonical, Ltd.
"""

Please update.

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

In Greeter.qml

"""
 d.startUnlock(false);
"""

This forces the reader to jump to the function definition to figure out what the heck that booelan means, hurting code readability.

Please at least add an inline comment saying what this boolean is about, like:

"""
d.startUnlock(false /* toTheRight */);
"""

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

Both in NarrowView.qml and in WideView.qml\

"""
    QtObject {
        id: d
    }
"""

The intention is good, but right now it's just dead code.

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

In CoverPager.qml:

"""
    property bool ready: greeterBackground.source == "" || greeterBackground.status == Image.Ready || greeterBackground.status == Image.Error
"""

This line is too long.

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

In tst_WideView.qml

"""
    Binding {
        target: LightDM.Users
        property: "mockMode"
        value: "full"
    }
"""

Shouldn't you also set LightDM.Greeter.mockMode to "full" as well? I'm not seing it being done anywhere in this file.

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

> In tst_WideView.qml
>
> """
> Binding {
> target: LightDM.Users
> property: "mockMode"
> value: "full"
> }
> """
>
> Shouldn't you also set LightDM.Greeter.mockMode to "full" as well? I'm not
> seing it being done anywhere in this file.

Hmm, maybe it doesn't matter as LightDM.Greeter doesn't seem to be used by WideView anyway.

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

In tst_Greeter.qml

"""
 * Copyright 2013 Canonical Ltd.
"""

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

tst_Greeter.tst_launcherOffsetAnimation is not really checking that the value got animated back to zero.

Suggestion:
http://paste.ubuntu.com/10204838/

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

In tests/plugins/LightDM/usersmodel.cpp

"""
 * Copyright (C) 2013 Canonical, Ltd.
"""

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

I think that would be all. Tested manually on N4 and N10 and didn't spot any obvious regressions.

lp:~mterry/unity8/greeter-refactor updated
1501. By Michael Terry

Merge from trunk

1502. By Michael Terry

Fix some review nits

1503. By Michael Terry

More nits

1504. By Michael Terry

More nits

1505. By Michael Terry

Fix shell tests, by reverting a small cleanup I tried to make

Revision history for this message
Michael Terry (mterry) wrote :

> """
> QtObject {
> id: d
> }
> """
>
> The intention is good, but right now it's just dead code.

I tend to use QtObject 'd' blocks like that as barriers between the "public" code and the implementation code. So they are useful as a visual separator, even if empty. But alright, removed.

> In tst_Greeter.qml [and usersmodel.cpp, Greeter.qml, and CoverPage.qml]
>
> """
> * Copyright 2013 Canonical Ltd.
> """

Fixed.

> """
> property bool ready: greeterBackground.source == "" || greeterBackground.status == Image.Ready || greeterBackground.status == Image.Error
> """
>
> This line is too long.

Fixed by removing it. It's not actually used anywhere now with this refactor.

> tst_Greeter.tst_launcherOffsetAnimation is not really checking that the value got animated back to zero.

Fair enough, used your version. Thanks!

> Now that the lightdm mock libs are unified we no longer need separate test files for different lightdm mock modes.

Fair enough, used your version. And added a line for "single-pin", though I didn't take the further step of consolidating tst_Shell.qml and tst_ShellWithPin.qml. That can be cleanup for some future branch.

> Shouldn't you also set LightDM.Greeter.mockMode to "full" as well? I'm not
> seing it being done anywhere in this file.
>
> Hmm, maybe it doesn't matter as LightDM.Greeter doesn't seem to be used by WideView anyway.

That's why I didn't have it. The views should never need the LightDM.Greeter object, they should get everything from the Greeter.qml class.

> Please at least add an inline comment saying what this boolean is about, like:
>
> """
> d.startUnlock(false /* toTheRight */);
> """

Done.

> """
> loader.item.authenticated(false);
> """
>
> Please name it like a function, instead of like a boolean property.

Fair, especially since LightDM.Greeter is already a property that exists in and around this code.

I've changed it to notifyAuthenticationFailed and notifyAuthenticationSucceeded (I don't like re-using the on* prefix because that same pattern is used for signals).

> I'm getting some test failures:
> [snip]

Fixed. I got overzealous in my consolidation of app-switching code in r1493.

=================

OK, that should be all your comments addressed.

Revision history for this message
Michael Terry (mterry) wrote :

"Fair, especially since LightDM.Greeter.authenticated is already a property that exists in and around this code."

is what I meant to say.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

Have to remove this line from tests/qmltests/CMakeLists.txt:

"""
add_qml_test(. ShellWithPassphrase)
"""

review: Needs Fixing
lp:~mterry/unity8/greeter-refactor updated
1506. By Michael Terry

Whoops, remove reference to ShellWithPassphrase

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Terry (mterry) wrote :

Yup, thanks. :P Fixed

lp:~mterry/unity8/greeter-refactor updated
1507. By Michael Terry

Don't enable backspace pin button when not supposed to

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

 * Did you perform an exploratory manual test run of the code change and any related functionality?
Yes.

 * Did CI run pass? If not, please explain why.
Unrelated, known, failures due to the move to qt 5.4 (I think)

 * Did you make sure that the branch does not contain spurious tags?
Yes

review: Approve
lp:~mterry/unity8/greeter-refactor updated
1508. By Michael Terry

Simplify behavior launcher/greeter/dash when doing a long-left-drag; now we never show dash while locked

Revision history for this message
Michael Zanetti (mzanetti) wrote :

Approving changed behavior in the last commit. Works fine in my opinion now. Also had a discussion with design about it and we're good.

 * Did you perform an exploratory manual test run of the code change and any related functionality?
Yes.

 * Did CI run pass? If not, please explain why.
Unrelated, known, failures due to the move to qt 5.4 (I think)

 * Did you make sure that the branch does not contain spurious tags?
Yes

review: Approve
Revision history for this message
Vesa Rautiainen (vesar) wrote :

> Approving changed behavior in the last commit. Works fine in my opinion now.
> Also had a discussion with design about it and we're good.
>
>
> * Did you perform an exploratory manual test run of the code change and any
> related functionality?
> Yes.
>
> * Did CI run pass? If not, please explain why.
> Unrelated, known, failures due to the move to qt 5.4 (I think)
>
> * Did you make sure that the branch does not contain spurious tags?
> Yes

Just to confirm here that Michael and I we had a chat about lockscreen blocking the long left edge swipe behaviour and that's all fine for design.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'cmake/modules/QmlTest.cmake'
--- cmake/modules/QmlTest.cmake 2014-12-11 15:48:35 +0000
+++ cmake/modules/QmlTest.cmake 2015-02-23 15:44:00 +0000
@@ -28,7 +28,7 @@
2828
29set(qmlscene_exe ${CMAKE_BINARY_DIR}/tests/uqmlscene/uqmlscene)29set(qmlscene_exe ${CMAKE_BINARY_DIR}/tests/uqmlscene/uqmlscene)
3030
31set(test_env UNITY_TESTING=1)31set(test_env LC_ALL=C UNITY_TESTING=1)
3232
33macro(add_manual_qml_test SUBPATH COMPONENT_NAME)33macro(add_manual_qml_test SUBPATH COMPONENT_NAME)
34 set(options NO_ADD_TEST NO_TARGETS)34 set(options NO_ADD_TEST NO_TARGETS)
3535
=== modified file 'qml/Components/DelayedLockscreen.qml'
--- qml/Components/DelayedLockscreen.qml 2014-09-16 00:00:06 +0000
+++ qml/Components/DelayedLockscreen.qml 2015-02-23 15:44:00 +0000
@@ -20,11 +20,13 @@
2020
21Item {21Item {
22 id: root22 id: root
23 anchors.fill: parent
2423
25 property int delayMinutes24 property int delayMinutes
26 property bool alphaNumeric25 property bool alphaNumeric
2726
27 signal entered(string passphrase) // unused
28 signal cancel() // unused
29
28 function clear(playAnimation) {}30 function clear(playAnimation) {}
2931
30 Column {32 Column {
3133
=== modified file 'qml/Components/PassphraseLockscreen.qml'
--- qml/Components/PassphraseLockscreen.qml 2014-10-23 13:23:07 +0000
+++ qml/Components/PassphraseLockscreen.qml 2015-02-23 15:44:00 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 2013 Canonical, Ltd.2 * Copyright (C) 2013,2014,2015 Canonical, Ltd.
3 *3 *
4 * This program is free software; you can redistribute it and/or modify4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by5 * it under the terms of the GNU General Public License as published by
@@ -20,8 +20,7 @@
2020
21Item {21Item {
22 id: root22 id: root
23 anchors.top: parent.top23 y: units.gu(4)
24 anchors.topMargin: units.gu(4)
25 height: shakeContainer.height24 height: shakeContainer.height
2625
27 property string infoText26 property string infoText
2827
=== modified file 'qml/Components/PinLockscreen.qml'
--- qml/Components/PinLockscreen.qml 2014-10-23 13:23:07 +0000
+++ qml/Components/PinLockscreen.qml 2015-02-23 15:44:00 +0000
@@ -21,9 +21,7 @@
2121
22Column {22Column {
23 id: root23 id: root
24 anchors.top: parent.top24 y: units.gu(4)
25 anchors.topMargin: units.gu(4)
26 anchors.horizontalCenter: parent.horizontalCenter
27 spacing: units.gu(2)25 spacing: units.gu(2)
2826
29 property string infoText27 property string infoText
@@ -123,6 +121,7 @@
123 objectName: "backspaceIcon"121 objectName: "backspaceIcon"
124 anchors { right: parent.right; top: parent.top; bottom: parent.bottom }122 anchors { right: parent.right; top: parent.top; bottom: parent.bottom }
125 width: height123 width: height
124 enabled: root.entryEnabled
126125
127 Icon {126 Icon {
128 anchors.fill: parent127 anchors.fill: parent
@@ -130,7 +129,7 @@
130 color: "#f3f3e7"129 color: "#f3f3e7"
131 }130 }
132131
133 opacity: (pinentryField.text.length && !pinentryField.incorrectOverride) > 0 ? 1 : 0132 opacity: (pinentryField.text.length > 0 && !pinentryField.incorrectOverride) ? 1 : 0
134133
135 Behavior on opacity {134 Behavior on opacity {
136 UbuntuNumberAnimation {}135 UbuntuNumberAnimation {}
137136
=== renamed file 'qml/Greeter/GreeterContent.qml' => 'qml/Greeter/CoverPage.qml'
--- qml/Greeter/GreeterContent.qml 2015-02-11 16:04:13 +0000
+++ qml/Greeter/CoverPage.qml 2015-02-23 15:44:00 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 2013 Canonical, Ltd.2 * Copyright (C) 2013,2014,2015 Canonical, Ltd.
3 *3 *
4 * This program is free software; you can redistribute it and/or modify4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by5 * it under the terms of the GNU General Public License as published by
@@ -14,34 +14,48 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */15 */
1616
17import QtQuick 2.017import QtQuick 2.3
18import AccountsService 0.118import Ubuntu.Components 1.1
19import Ubuntu.Components 0.119import Ubuntu.Gestures 0.1
20import LightDM 0.1 as LightDM
21import "../Components"20import "../Components"
2221
23Item {22Showable {
24 id: root23 id: root
25 anchors.fill: parent24
2625 property real dragHandleLeftMargin
27 property var inputMethod26 property real launcherOffset
2827 property alias background: greeterBackground.source
29 property bool ready: background.source == "" || background.status == Image.Ready || background.status == Image.Error28 property real backgroundTopMargin
3029 property var infographicModel
31 signal selected(int uid)30 property bool draggable: true
32 signal unlocked(int uid)31
3332 property alias infographics: infographics
34 function tryToUnlock() {33
35 if (loginLoader.item) {34 readonly property real showProgress: MathUtils.clamp((width - Math.abs(x)) / width, 0, 1)
36 loginLoader.item.tryToUnlock()35
37 }36 signal tease()
38 }37
3938 function hideRight() {
40 function reset() {39 d.forceRightOnNextHideAnimation = true;
41 if (loginLoader.item) {40 hide();
42 loginLoader.item.reset()41 }
43 }42
44 }43 QtObject {
44 id: d
45 property bool forceRightOnNextHideAnimation: false
46 }
47
48 prepareToHide: function () {
49 hideTranslation.from = root.x + translation.x
50 hideTranslation.to = root.x > 0 || d.forceRightOnNextHideAnimation ? root.width : -root.width;
51 d.forceRightOnNextHideAnimation = false;
52 }
53
54 // We don't directly bind "x" because that's owned by the DragHandle. So
55 // instead, we can get a little extra horizontal push by using transforms.
56 transform: Translate { id: translation; x: root.draggable ? launcherOffset : 0 }
57
58 MouseArea { anchors.fill: parent; }
4559
46 Rectangle {60 Rectangle {
47 // In case background fails to load61 // In case background fails to load
@@ -51,17 +65,16 @@
51 }65 }
5266
53 CrossFadeImage {67 CrossFadeImage {
54 id: background68 id: greeterBackground
55 objectName: "greeterBackground"69 objectName: "greeterBackground"
56 anchors {70 anchors {
57 fill: parent71 fill: parent
58 topMargin: backgroundTopMargin72 topMargin: root.backgroundTopMargin
59 }73 }
60 fillMode: Image.PreserveAspectCrop74 fillMode: Image.PreserveAspectCrop
61 // Limit how much memory we'll reserve for this image75 // Limit how much memory we'll reserve for this image
62 sourceSize.height: height76 sourceSize.height: height
63 sourceSize.width: width77 sourceSize.width: width
64 source: greeter.background
65 }78 }
6679
67 Rectangle {80 Rectangle {
@@ -70,94 +83,134 @@
70 opacity: 0.483 opacity: 0.4
71 }84 }
7285
73 Loader {
74 id: loginLoader
75 objectName: "loginLoader"
76 anchors {
77 left: parent.left
78 leftMargin: Math.min(parent.width * 0.16, units.gu(20))
79 top: parent.top
80 }
81 width: units.gu(29)
82 height: inputMethod && inputMethod.visible ? parent.height - inputMethod.keyboardRectangle.height
83 : parent.height
84 Behavior on height { UbuntuNumberAnimation {} }
85
86 // TODO: Once we have a system API for determining which mode we are
87 // in, tablet/phone/desktop, that should be used instead of narrowMode.
88 source: greeter.narrowMode ? "" : "LoginList.qml"
89
90 onLoaded: {
91 item.currentIndex = greeterContentLoader.currentIndex;
92 }
93
94 Binding {
95 target: loginLoader.item
96 property: "model"
97 value: greeterContentLoader.model
98 }
99
100 Connections {
101 target: loginLoader.item
102
103 onSelected: {
104 root.selected(uid);
105 }
106
107 onUnlocked: {
108 root.unlocked(uid);
109 }
110
111 onCurrentIndexChanged: {
112 if (greeterContentLoader.currentIndex !== loginLoader.item.currentIndex) {
113 greeterContentLoader.currentIndex = loginLoader.item.currentIndex;
114 }
115 }
116 }
117 }
118
119 Infographics {86 Infographics {
120 id: infographics87 id: infographics
121 objectName: "infographics"88 objectName: "infographics"
122 height: narrowMode ? parent.height : 0.75 * parent.height89 height: parent.height
123 model: greeterContentLoader.infographicModel90 model: root.infographicModel
124 clip: true // clip large data bubbles91 clip: true // clip large data bubbles
12592
126 property string selectedUser
127 property string infographicUser: AccountsService.statsWelcomeScreen ? selectedUser : ""
128 onInfographicUserChanged: greeterContentLoader.infographicModel.username = infographicUser
129
130 Component.onCompleted: {
131 selectedUser = greeterContentLoader.model.data(greeterContentLoader.currentIndex, LightDM.UserRoles.NameRole)
132 greeterContentLoader.infographicModel.username = infographicUser
133 greeterContentLoader.infographicModel.readyForDataChange()
134 }
135
136 Connections {
137 target: root
138 onSelected: infographics.selectedUser = greeterContentLoader.model.data(uid, LightDM.UserRoles.NameRole)
139 }
140
141 Connections {
142 target: i18n
143 onLanguageChanged: greeterContentLoader.infographicModel.readyForDataChange()
144 }
145
146 anchors {93 anchors {
147 verticalCenter: parent.verticalCenter94 verticalCenter: parent.verticalCenter
148 left: narrowMode ? root.left : loginLoader.right95 left: parent.left
149 right: root.right96 right: parent.right
150 }97 }
151 }98 }
15299
153 Clock {100 Label {
154 id: clock101 id: swipeHint
155 visible: narrowMode102 property real baseOpacity: 0.5
156103 opacity: 0.0
157 anchors {104 anchors.horizontalCenter: parent.horizontalCenter
158 top: parent.top105 anchors.bottom: parent.bottom
159 topMargin: units.gu(2)106 anchors.bottomMargin: units.gu(5)
160 horizontalCenter: parent.horizontalCenter107 text: "《 " + i18n.tr("Unlock") + " 》"
108 color: "white"
109 font.weight: Font.Light
110
111 SequentialAnimation on opacity {
112 id: showLabelAnimation
113 running: false
114 loops: 2
115
116 StandardAnimation {
117 from: 0.0
118 to: swipeHint.baseOpacity
119 duration: UbuntuAnimation.SleepyDuration
120 }
121 PauseAnimation { duration: UbuntuAnimation.BriskDuration }
122 StandardAnimation {
123 from: swipeHint.baseOpacity
124 to: 0.0
125 duration: UbuntuAnimation.SleepyDuration
126 }
127 }
128 }
129
130 DragHandle {
131 id: dragHandle
132 anchors.fill: parent
133 anchors.leftMargin: root.dragHandleLeftMargin
134 enabled: root.draggable
135 direction: Direction.Horizontal
136
137 onDraggingChanged: {
138 if (dragging) {
139 root.tease();
140 showLabelAnimation.start();
141 }
142 }
143 }
144
145 // right side shadow
146 Image {
147 anchors.left: parent.right
148 anchors.top: parent.top
149 anchors.bottom: parent.bottom
150 fillMode: Image.Tile
151 source: "../graphics/dropshadow_right.png"
152 }
153
154 // left side shadow
155 Image {
156 anchors.right: parent.left
157 anchors.top: parent.top
158 anchors.bottom: parent.bottom
159 fillMode: Image.Tile
160 source: "../graphics/dropshadow_left.png"
161 }
162
163 Binding {
164 id: positionLock
165
166 property bool enabled: false
167 onEnabledChanged: {
168 if (enabled === __enabled) {
169 return;
170 }
171
172 if (enabled) {
173 if (root.x > 0) {
174 value = Qt.binding(function() { return root.width; })
175 } else {
176 value = Qt.binding(function() { return -root.width; })
177 }
178 }
179
180 __enabled = enabled;
181 }
182
183 property bool __enabled: false
184
185 target: root
186 when: __enabled
187 property: "x"
188 }
189
190 hideAnimation: SequentialAnimation {
191 id: hideAnimation
192 objectName: "hideAnimation"
193 property var target // unused, here to silence Showable warning
194 StandardAnimation {
195 id: hideTranslation
196 property: "x"
197 target: root
198 }
199 PropertyAction { target: root; property: "visible"; value: false }
200 PropertyAction { target: positionLock; property: "enabled"; value: true }
201 }
202
203 showAnimation: SequentialAnimation {
204 id: showAnimation
205 objectName: "showAnimation"
206 property var target // unused, here to silence Showable warning
207 PropertyAction { target: root; property: "visible"; value: true }
208 PropertyAction { target: positionLock; property: "enabled"; value: false }
209 StandardAnimation {
210 property: "x"
211 target: root
212 to: 0
213 duration: UbuntuAnimation.FastDuration
161 }214 }
162 }215 }
163}216}
164217
=== modified file 'qml/Greeter/Greeter.qml'
--- qml/Greeter/Greeter.qml 2015-01-23 19:47:37 +0000
+++ qml/Greeter/Greeter.qml 2015-02-23 15:44:00 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 2013 Canonical, Ltd.2 * Copyright (C) 2013,2014,2015 Canonical, Ltd.
3 *3 *
4 * This program is free software; you can redistribute it and/or modify4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by5 * it under the terms of the GNU General Public License as published by
@@ -14,66 +14,185 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */15 */
1616
17import QtQuick 2.017import QtQuick 2.3
18import Ubuntu.Components 0.118import AccountsService 0.1
19import Ubuntu.Gestures 0.1
20import LightDM 0.1 as LightDM19import LightDM 0.1 as LightDM
20import Ubuntu.Components 1.1
21import Ubuntu.SystemImage 0.1
22import Unity.Connectivity 0.1
23import Unity.Launcher 0.1
21import "../Components"24import "../Components"
2225
23Showable {26Showable {
24 id: greeter27 id: root
25 enabled: shown28 created: loader.status == Loader.Ready
26 created: greeterContentLoader.status == Loader.Ready && greeterContentLoader.item.ready
2729
28 property real dragHandleLeftMargin: 030 property real dragHandleLeftMargin: 0
29 property alias dragging: dragHandle.dragging
3031
31 property url background32 property url background
3233
33 // so that it can be replaced in tests with a mock object34 // How far to offset the top greeter layer during a launcher left-drag
34 property var inputMethod: Qt.inputMethod35 property real launcherOffset
3536
36 prepareToHide: function () {37 readonly property bool active: shown || hasLockedApp
37 hideTranslation.to = greeter.x > 0 || d.forceRightOnNextHideAnimation ? greeter.width : -greeter.width;38 readonly property bool fullyShown: loader.item ? loader.item.fullyShown : false
38 d.forceRightOnNextHideAnimation = false;39
40 // True when the greeter is waiting for PAM or other setup process
41 readonly property alias waiting: d.waiting
42
43 property string lockedApp: ""
44 readonly property bool hasLockedApp: lockedApp !== ""
45
46 property bool forcedUnlock
47 readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated && !forcedUnlock
48
49 property bool tabletMode
50 property url viewSource // only used for testing
51
52 property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
53 property int failedLoginsDelayAttempts: 7 // number of failed logins
54 property int failedLoginsDelayMinutes: 5 // minutes of forced waiting
55
56 signal tease()
57 signal sessionStarted()
58 signal emergencyCall()
59
60 function forceShow() {
61 showNow();
62 loader.item.reset();
63 }
64
65 function notifyAppFocused(appId) {
66 if (!active) {
67 return;
68 }
69
70 if (hasLockedApp) {
71 if (appId === lockedApp) {
72 hide(); // show locked app
73 } else {
74 show();
75 d.startUnlock(false /* toTheRight */);
76 }
77 } else if (appId !== "unity8-dash") { // dash isn't started by user
78 d.startUnlock(false /* toTheRight */);
79 }
80 }
81
82 function notifyAboutToFocusApp(appId) {
83 if (!active) {
84 return;
85 }
86
87 // A hint that we're about to focus an app. This way we can look
88 // a little more responsive, rather than waiting for the above
89 // notifyAppFocused call. We also need this in case we have a locked
90 // app, in order to show lockscreen instead of new app.
91 d.startUnlock(false /* toTheRight */);
92 }
93
94 // This is a just a glorified notifyAboutToFocusApp(), but it does one
95 // other thing: it hides any cover pages to the RIGHT, because the user
96 // just came from a launcher drag starting on the left.
97 // It also returns a boolean value, indicating whether there was a visual
98 // change or not (the shell only wants to hide the launcher if there was
99 // a change).
100 function notifyShowingDashFromDrag() {
101 if (!active) {
102 return false;
103 }
104
105 return d.startUnlock(true /* toTheRight */);
39 }106 }
40107
41 QtObject {108 QtObject {
42 id: d109 id: d
43 property bool forceRightOnNextHideAnimation: false110
44 }111 readonly property bool multiUser: LightDM.Users.count > 1
45112 property int currentIndex
46 property bool loadContent: required113 property int delayMinutes
47114 property bool waiting
48 // 1 when fully shown and 0 when fully hidden115
49 property real showProgress: visible ? MathUtils.clamp((width - Math.abs(x)) / width, 0, 1) : 0116 // We want 'launcherOffset' to animate down to zero. But not to animate
50117 // while being dragged. So ideally we change this only when the user
51 property alias model: greeterContentLoader.model118 // lets go and launcherOffset drops to zero. But we need to wait for
52 property bool locked: true119 // the behavior to be enabled first. So we cache the last known good
53120 // launcherOffset value to cover us during that brief gap between
54 readonly property bool narrowMode: !multiUser && height > width121 // release and the behavior turning on.
55 readonly property bool multiUser: LightDM.Users.count > 1122 property real lastKnownPositiveOffset // set in a launcherOffsetChanged below
56123 property real launcherOffsetProxy: (shown && !launcherOffsetProxyBehavior.enabled) ? lastKnownPositiveOffset : 0
57 readonly property int currentIndex: greeterContentLoader.currentIndex124 Behavior on launcherOffsetProxy {
58125 id: launcherOffsetProxyBehavior
59 signal selected(int uid)126 enabled: launcherOffset === 0
60 signal unlocked(int uid)127 UbuntuNumberAnimation {}
61 signal tapped()128 }
62129
63 function hideRight() {130 function selectUser(uid, reset) {
64 d.forceRightOnNextHideAnimation = true;131 d.waiting = true;
65 hide();132 if (reset) {
66 }133 loader.item.reset();
67134 }
68 function tryToUnlock() {135 currentIndex = uid;
69 if (created) {136 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
70 greeterContentLoader.item.tryToUnlock()137 AccountsService.user = user;
71 }138 LauncherModel.setUser(user);
72 }139 LightDM.Greeter.authenticate(user); // always resets auth state
73140 }
74 function reset() {141
75 if (created) {142 function login() {
76 greeterContentLoader.item.reset()143 enabled = false;
144 if (LightDM.Greeter.startSessionSync()) {
145 sessionStarted();
146 loader.item.notifyAuthenticationSucceeded();
147 } else {
148 loader.item.notifyAuthenticationFailed();
149 }
150 enabled = true;
151 }
152
153 function startUnlock(toTheRight) {
154 if (loader.item) {
155 return loader.item.tryToUnlock(toTheRight);
156 } else {
157 return false;
158 }
159 }
160 }
161
162 onLauncherOffsetChanged: {
163 if (launcherOffset > 0) {
164 d.lastKnownPositiveOffset = launcherOffset;
165 }
166 }
167
168 onForcedUnlockChanged: {
169 if (forcedUnlock && shown) {
170 // pretend we were just authenticated
171 loader.item.notifyAuthenticationSucceeded();
172 }
173 }
174
175 onRequiredChanged: {
176 if (required) {
177 d.waiting = true;
178 lockedApp = "";
179 }
180 }
181
182 Component.onCompleted: {
183 Connectivity.unlockAllModems();
184 }
185
186 Timer {
187 id: forcedDelayTimer
188 interval: 1000 * 60
189 onTriggered: {
190 if (d.delayMinutes > 0) {
191 d.delayMinutes -= 1;
192 if (d.delayMinutes > 0) {
193 start(); // go again
194 }
195 }
77 }196 }
78 }197 }
79198
@@ -82,152 +201,205 @@
82 MouseArea { anchors.fill: parent }201 MouseArea { anchors.fill: parent }
83202
84 Loader {203 Loader {
85 id: greeterContentLoader204 id: loader
86 objectName: "greeterContentLoader"205 objectName: "loader"
206
87 anchors.fill: parent207 anchors.fill: parent
88 property var model: LightDM.Users
89 property int currentIndex: 0
90 property var infographicModel: LightDM.Infographic
91 readonly property int backgroundTopMargin: -greeter.y
92208
93 source: loadContent ? "GreeterContent.qml" : ""209 active: root.required
210 source: root.viewSource.toString() ? root.viewSource :
211 (d.multiUser || root.tabletMode) ? "WideView.qml" : "NarrowView.qml"
94212
95 onLoaded: {213 onLoaded: {
96 selected(currentIndex);214 root.lockedApp = "";
215 root.forceActiveFocus();
216 d.selectUser(d.currentIndex, true);
217 LightDM.Infographic.readyForDataChange();
97 }218 }
98219
99 Connections {220 Connections {
100 target: greeterContentLoader.item221 target: loader.item
101
102 onSelected: {222 onSelected: {
103 greeter.selected(uid);223 d.selectUser(index, true);
104 greeterContentLoader.currentIndex = uid;224 }
105 }225 onResponded: {
106 onUnlocked: greeter.unlocked(uid);226 if (root.locked) {
107 }227 LightDM.Greeter.respond(response);
108 Binding {
109 target: greeterContentLoader.item
110 property: "inputMethod"
111 value: greeter.inputMethod
112 }
113 }
114
115 DragHandle {
116 id: dragHandle
117 anchors.fill: parent
118 anchors.leftMargin: greeter.dragHandleLeftMargin
119 enabled: (greeter.narrowMode || !greeter.locked) && greeter.enabled && greeter.shown
120 direction: Direction.Horizontal
121
122 onTapped: {
123 greeter.tapped();
124 showLabelAnimation.start();
125 }
126
127 onDraggingChanged: {
128 if (dragging) {
129 showLabelAnimation.start();
130 }
131 }
132 }
133
134 Label {
135 id: swipeHint
136 property real baseOpacity: 0.5
137 opacity: 0.0
138 anchors.horizontalCenter: parent.horizontalCenter
139 anchors.bottom: parent.bottom
140 anchors.bottomMargin: units.gu(5)
141 text: "《 " + i18n.tr("Unlock") + " 》"
142 color: "white"
143 font.weight: Font.Light
144
145 SequentialAnimation on opacity {
146 id: showLabelAnimation
147 running: false
148 loops: 2
149
150 StandardAnimation {
151 from: 0.0
152 to: swipeHint.baseOpacity
153 duration: UbuntuAnimation.SleepyDuration
154 }
155 PauseAnimation { duration: UbuntuAnimation.BriskDuration }
156 StandardAnimation {
157 from: swipeHint.baseOpacity
158 to: 0.0
159 duration: UbuntuAnimation.SleepyDuration
160 }
161 }
162 }
163
164 // right side shadow
165 Image {
166 anchors.left: parent.right
167 anchors.top: parent.top
168 anchors.bottom: parent.bottom
169 fillMode: Image.Tile
170 source: "../graphics/dropshadow_right.png"
171 }
172
173 // left side shadow
174 Image {
175 anchors.right: parent.left
176 anchors.top: parent.top
177 anchors.bottom: parent.bottom
178 fillMode: Image.Tile
179 source: "../graphics/dropshadow_left.png"
180 }
181
182 Binding {
183 id: positionLock
184
185 property bool enabled: false
186 onEnabledChanged: {
187 if (enabled === __enabled) {
188 return;
189 }
190
191 if (enabled) {
192 if (greeter.x > 0) {
193 value = Qt.binding(function() { return greeter.width; })
194 } else {228 } else {
195 value = Qt.binding(function() { return -greeter.width; })229 if (LightDM.Greeter.active && !LightDM.Greeter.authenticated) { // could happen if forcedUnlock
196 }230 d.login();
197 }231 }
198232 loader.item.hide();
199 __enabled = enabled;233 }
200 }234 }
201235 onTease: root.tease()
202 property bool __enabled: false236 onEmergencyCall: root.emergencyCall()
203237 onRequiredChanged: {
204 target: greeter238 if (!loader.item.required) {
205 when: __enabled239 root.hide();
206 property: "x"240 }
207 }241 }
208242 }
209 hideAnimation: SequentialAnimation {243
210 id: hideAnimation244 Binding {
211 objectName: "hideAnimation"245 target: loader.item
212 StandardAnimation {246 property: "backgroundTopMargin"
213 id: hideTranslation247 value: -root.y
214 property: "x"248 }
215 target: greeter249
216 }250 Binding {
217 PropertyAction { target: greeter; property: "visible"; value: false }251 target: loader.item
218 PropertyAction { target: positionLock; property: "enabled"; value: true }252 property: "launcherOffset"
219 }253 value: d.launcherOffsetProxy
220254 }
221 showAnimation: SequentialAnimation {255
222 id: showAnimation256 Binding {
223 objectName: "showAnimation"257 target: loader.item
224 PropertyAction { target: greeter; property: "visible"; value: true }258 property: "dragHandleLeftMargin"
225 PropertyAction { target: positionLock; property: "enabled"; value: false }259 value: root.dragHandleLeftMargin
226 StandardAnimation {260 }
227 property: "x"261
228 target: greeter262 Binding {
229 to: 0263 target: loader.item
230 duration: UbuntuAnimation.FastDuration264 property: "delayMinutes"
231 }265 value: d.delayMinutes
266 }
267
268 Binding {
269 target: loader.item
270 property: "background"
271 value: root.background
272 }
273
274 Binding {
275 target: loader.item
276 property: "locked"
277 value: root.locked
278 }
279
280 Binding {
281 target: loader.item
282 property: "alphanumeric"
283 value: AccountsService.passwordDisplayHint === AccountsService.Keyboard
284 }
285
286 Binding {
287 target: loader.item
288 property: "currentIndex"
289 value: d.currentIndex
290 }
291
292 Binding {
293 target: loader.item
294 property: "userModel"
295 value: LightDM.Users
296 }
297
298 Binding {
299 target: loader.item
300 property: "infographicModel"
301 value: LightDM.Infographic
302 }
303 }
304
305 Connections {
306 target: LightDM.Greeter
307
308 onShowGreeter: root.forceShow()
309
310 onHideGreeter: {
311 d.login();
312 loader.item.hide();
313 }
314
315 onShowMessage: {
316 if (!LightDM.Greeter.active) {
317 return; // could happen if hideGreeter() comes in before we prompt
318 }
319
320 // inefficient, but we only rarely deal with messages
321 var html = text.replace(/&/g, "&amp;")
322 .replace(/</g, "&lt;")
323 .replace(/>/g, "&gt;")
324 .replace(/\n/g, "<br>");
325 if (isError) {
326 html = "<font color=\"#df382c\">" + html + "</font>";
327 }
328
329 loader.item.showMessage(html);
330 }
331
332 onShowPrompt: {
333 d.waiting = false;
334
335 if (!LightDM.Greeter.active) {
336 return; // could happen if hideGreeter() comes in before we prompt
337 }
338
339 loader.item.showPrompt(text, isSecret, isDefaultPrompt);
340 }
341
342 onAuthenticationComplete: {
343 d.waiting = false;
344
345 if (LightDM.Greeter.authenticated) {
346 AccountsService.failedLogins = 0;
347 d.login();
348 if (!LightDM.Greeter.promptless) {
349 loader.item.hide();
350 }
351 } else {
352 if (!LightDM.Greeter.promptless) {
353 AccountsService.failedLogins++;
354 }
355
356 // Check if we should initiate a factory reset
357 if (maxFailedLogins >= 2) { // require at least a warning
358 if (AccountsService.failedLogins === maxFailedLogins - 1) {
359 loader.item.showLastChance();
360 } else if (AccountsService.failedLogins >= maxFailedLogins) {
361 SystemImage.factoryReset(); // Ouch!
362 }
363 }
364
365 // Check if we should initiate a forced login delay
366 if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
367 d.delayMinutes = failedLoginsDelayMinutes;
368 forcedDelayTimer.start();
369 }
370
371 loader.item.notifyAuthenticationFailed();
372 if (!LightDM.Greeter.promptless) {
373 d.selectUser(d.currentIndex, false);
374 }
375 }
376 }
377
378 onRequestAuthenticationUser: {
379 // Find index for requested user, if it exists
380 for (var i = 0; i < LightDM.Users.count; i++) {
381 if (user === LightDM.Users.data(i, LightDM.UserRoles.NameRole)) {
382 d.selectUser(i, true);
383 return;
384 }
385 }
386 }
387 }
388
389 Binding {
390 target: LightDM.Greeter
391 property: "active"
392 value: root.active
393 }
394
395 Binding {
396 target: LightDM.Infographic
397 property: "username"
398 value: AccountsService.statsWelcomeScreen ? LightDM.Users.data(d.currentIndex, LightDM.UserRoles.NameRole) : ""
399 }
400
401 Connections {
402 target: i18n
403 onLanguageChanged: LightDM.Infographic.readyForDataChange()
232 }404 }
233}405}
234406
=== modified file 'qml/Greeter/Infographics.qml'
--- qml/Greeter/Infographics.qml 2015-01-09 10:41:28 +0000
+++ qml/Greeter/Infographics.qml 2015-02-23 15:44:00 +0000
@@ -38,6 +38,7 @@
3838
39 Connections {39 Connections {
40 target: model40 target: model
41 ignoreUnknownSignals: model === undefined
4142
42 onDataAboutToAppear: startHideAnimation() // hide "no data" label43 onDataAboutToAppear: startHideAnimation() // hide "no data" label
43 onDataAppeared: startShowAnimation()44 onDataAppeared: startShowAnimation()
4445
=== modified file 'qml/Greeter/LoginList.qml'
--- qml/Greeter/LoginList.qml 2014-11-04 12:52:49 +0000
+++ qml/Greeter/LoginList.qml 2015-02-23 15:44:00 +0000
@@ -14,44 +14,76 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */15 */
1616
17import QtQuick 2.017import QtQuick 2.3
18import Ubuntu.Components 0.118import Ubuntu.Components 1.1
19import LightDM 0.1 as LightDM
20import "../Components"19import "../Components"
2120
22Item {21Item {
23 id: root22 id: root
2423
25 property alias userList: userList
26 property alias model: userList.model24 property alias model: userList.model
27 property alias currentIndex: userList.currentIndex25 property int currentIndex
26 property bool locked
2827
29 readonly property int numAboveBelow: 428 readonly property int numAboveBelow: 4
30 readonly property int cellHeight: units.gu(5)29 readonly property int cellHeight: units.gu(5)
31 readonly property int highlightedHeight: units.gu(10)30 readonly property int highlightedHeight: units.gu(10)
32 readonly property int moveDuration: 20031 readonly property int moveDuration: 200
32 readonly property string currentUser: userList.currentItem.username
33 property bool wasPrompted: false33 property bool wasPrompted: false
3434
35 signal selected(int uid)35 signal selected(int index)
36 signal unlocked(int uid)36 signal responded(string response)
3737
38 function tryToUnlock() {38 function tryToUnlock() {
39 if (LightDM.Greeter.promptless) {39 if (wasPrompted) {
40 if (LightDM.Greeter.authenticated) {40 passwordInput.forceActiveFocus();
41 root.unlocked(userList.currentIndex)41 } else {
42 if (root.locked) {
43 root.selected(currentIndex);
42 } else {44 } else {
43 root.resetAuthentication()45 root.responded("");
44 }46 }
47 }
48 }
49
50 function showMessage(html) {
51 if (infoLabel.text === "") {
52 infoLabel.text = html;
45 } else {53 } else {
46 passwordInput.forceActiveFocus()54 infoLabel.text += "<br>" + html;
55 }
56 }
57
58 function showPrompt(text, isSecret, isDefaultPrompt) {
59 passwordInput.text = "";
60 passwordInput.promptText = text;
61 passwordInput.enabled = true;
62 passwordInput.echoMode = isSecret ? TextInput.Password : TextInput.Normal;
63 if (wasPrompted) // stay in text field if second prompt
64 passwordInput.focus = true;
65 wasPrompted = true;
66 }
67
68 function showError() {
69 wrongPasswordAnimation.start();
70 root.resetAuthentication();
71 if (wasPrompted) {
72 passwordInput.focus = true;
47 }73 }
48 }74 }
4975
50 function reset() {76 function reset() {
51 root.resetAuthentication()77 root.resetAuthentication();
52 }78 }
5379
54 Keys.onEscapePressed: root.resetAuthentication()80 Keys.onEscapePressed: {
81 selected(currentIndex);
82 }
83
84 onCurrentIndexChanged: {
85 userList.currentIndex = currentIndex;
86 }
5587
56 Rectangle {88 Rectangle {
57 id: highlightItem89 id: highlightItem
@@ -81,26 +113,24 @@
81 flickDeceleration: 10000113 flickDeceleration: 10000
82114
83 readonly property bool movingInternally: moveTimer.running || userList.moving115 readonly property bool movingInternally: moveTimer.running || userList.moving
84
85 onCurrentIndexChanged: {
86 if (LightDM.Greeter.authenticationUser != userList.model.data(currentIndex, LightDM.UserRoles.NameRole)) {
87 root.resetAuthentication();
88 }
89 }
90
91 onMovingInternallyChanged: {116 onMovingInternallyChanged: {
92 // Only emit the selected signal once we stop moving to avoid frequent background changes
93 if (!movingInternally) {117 if (!movingInternally) {
94 root.selected(userList.currentIndex);118 root.selected(currentIndex);
95 }119 }
96 }120 }
97121
122 onCurrentIndexChanged: {
123 root.resetAuthentication();
124 moveTimer.start();
125 }
126
98 delegate: Item {127 delegate: Item {
99 width: parent.width128 width: parent.width
100 height: root.cellHeight129 height: root.cellHeight
101130
102 readonly property bool belowHighlight: (userList.currentIndex < 0 && index > 0) || (userList.currentIndex >= 0 && index > userList.currentIndex)131 readonly property bool belowHighlight: (userList.currentIndex < 0 && index > 0) || (userList.currentIndex >= 0 && index > userList.currentIndex)
103 readonly property int belowOffset: root.highlightedHeight - root.cellHeight132 readonly property int belowOffset: root.highlightedHeight - root.cellHeight
133 readonly property string username: name
104134
105 opacity: {135 opacity: {
106 // The goal here is to make names less and less opaque as they136 // The goal here is to make names less and less opaque as they
@@ -148,11 +178,8 @@
148 topMargin: parent.belowHighlight ? parent.belowOffset : 0178 topMargin: parent.belowHighlight ? parent.belowOffset : 0
149 }179 }
150 height: parent.height180 height: parent.height
151 enabled: userList.currentIndex !== index181 enabled: userList.currentIndex !== index && parent.opacity > 0
152 onClicked: {182 onClicked: root.selected(index)
153 moveTimer.start();
154 userList.currentIndex = index;
155 }
156183
157 Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }184 Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
158 }185 }
@@ -204,6 +231,11 @@
204 width: parent.width - anchors.margins * 2231 width: parent.width - anchors.margins * 2
205 opacity: userList.movingInternally ? 0 : 1232 opacity: userList.movingInternally ? 0 : 1
206233
234 property string promptText
235 placeholderText: root.wasPrompted ? promptText
236 : (root.locked ? i18n.tr("Retry")
237 : i18n.tr("Tap to unlock"))
238
207 Behavior on opacity {239 Behavior on opacity {
208 NumberAnimation { duration: 100 }240 NumberAnimation { duration: 100 }
209 }241 }
@@ -213,9 +245,11 @@
213 return;245 return;
214 root.focus = true; // so that it can handle Escape presses for us246 root.focus = true; // so that it can handle Escape presses for us
215 enabled = false;247 enabled = false;
216 LightDM.Greeter.respond(text);248 root.responded(text);
217 }249 }
218 Keys.onEscapePressed: root.resetAuthentication()250 Keys.onEscapePressed: {
251 root.selected(currentIndex);
252 }
219253
220 Image {254 Image {
221 anchors {255 anchors {
@@ -223,7 +257,7 @@
223 rightMargin: units.gu(2)257 rightMargin: units.gu(2)
224 verticalCenter: parent.verticalCenter258 verticalCenter: parent.verticalCenter
225 }259 }
226 visible: LightDM.Greeter.promptless260 visible: !root.wasPrompted
227 source: "graphics/icon_arrow.png"261 source: "graphics/icon_arrow.png"
228 }262 }
229263
@@ -247,7 +281,7 @@
247 id: passwordMouseArea281 id: passwordMouseArea
248 objectName: "passwordMouseArea"282 objectName: "passwordMouseArea"
249 anchors.fill: passwordInput283 anchors.fill: passwordInput
250 enabled: LightDM.Greeter.promptless284 enabled: !root.wasPrompted
251 onClicked: root.tryToUnlock()285 onClicked: root.tryToUnlock()
252 }286 }
253287
@@ -256,62 +290,10 @@
256 return;290 return;
257 }291 }
258 infoLabel.text = "";292 infoLabel.text = "";
259 passwordInput.placeholderText = "";293 passwordInput.promptText = "";
260 passwordInput.text = "";294 passwordInput.text = "";
261 passwordInput.focus = false;295 passwordInput.focus = false;
262 passwordInput.enabled = true;296 passwordInput.enabled = true;
263 root.wasPrompted = false;297 root.wasPrompted = false;
264 LightDM.Greeter.authenticate(userList.model.data(currentIndex, LightDM.UserRoles.NameRole));
265 }
266
267 Connections {
268 target: LightDM.Greeter
269
270 onShowMessage: {
271 // inefficient, but we only rarely deal with messages
272 var html = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\n/g, "<br>")
273 if (isError)
274 html = "<font color=\"#df382c\">" + html + "</font>"
275 if (infoLabel.text == "")
276 infoLabel.text = html
277 else
278 infoLabel.text = infoLabel.text + "<br>" + html
279 }
280
281 onShowPrompt: {
282 passwordInput.text = "";
283 passwordInput.placeholderText = text;
284 passwordInput.enabled = true;
285 passwordInput.echoMode = isSecret ? TextInput.Password : TextInput.Normal;
286 if (root.wasPrompted) // stay in text field if second prompt
287 passwordInput.focus = true;
288 root.wasPrompted = true;
289 }
290
291 onAuthenticationComplete: {
292 if (LightDM.Greeter.promptless) {
293 passwordInput.placeholderText = LightDM.Greeter.authenticated ? "Tap to unlock" : "Retry";
294 return;
295 }
296 if (LightDM.Greeter.authenticated) {
297 root.unlocked(userList.currentIndex);
298 } else {
299 wrongPasswordAnimation.start();
300 root.resetAuthentication();
301 passwordInput.focus = true;
302 }
303 passwordInput.text = "";
304 }
305
306 onRequestAuthenticationUser: {
307 // Find index for requested user, if it exists
308 for (var i = 0; i < userList.model.count; i++) {
309 if (user == userList.model.data(i, LightDM.UserRoles.NameRole)) {
310 moveTimer.start();
311 userList.currentIndex = i;
312 return;
313 }
314 }
315 }
316 }298 }
317}299}
318300
=== added file 'qml/Greeter/NarrowView.qml'
--- qml/Greeter/NarrowView.qml 1970-01-01 00:00:00 +0000
+++ qml/Greeter/NarrowView.qml 2015-02-23 15:44:00 +0000
@@ -0,0 +1,170 @@
1/*
2 * Copyright (C) 2015 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.3
18import Ubuntu.Components 1.1
19import "../Components"
20
21FocusScope {
22 id: root
23
24 property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
25 property alias launcherOffset: coverPage.launcherOffset
26 property int currentIndex // unused
27 property alias delayMinutes: lockscreen.delayMinutes
28 property alias backgroundTopMargin: coverPage.backgroundTopMargin
29 property url background
30 property bool locked
31 property bool alphanumeric
32 property var userModel // unused
33 property alias infographicModel: coverPage.infographicModel
34 readonly property bool fullyShown: coverPage.showProgress === 1 || lockscreen.shown
35 readonly property bool required: coverPage.required || lockscreen.required
36
37 signal selected(int index) // unused
38 signal responded(string response)
39 signal tease()
40 signal emergencyCall()
41
42 function showMessage(html) {
43 // TODO
44 }
45
46 function showPrompt(text, isSecret, isDefaultPrompt) {
47 lockscreen.promptText = isDefaultPrompt ? "" : text.toLowerCase();
48 lockscreen.maybeShow();
49 }
50
51 function showLastChance() {
52 var title = lockscreen.alphaNumeric ?
53 i18n.tr("Sorry, incorrect passphrase.") :
54 i18n.tr("Sorry, incorrect passcode.");
55 var text = i18n.tr("This will be your last attempt.") + " " +
56 (lockscreen.alphaNumeric ?
57 i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
58 i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."));
59 lockscreen.showInfoPopup(title, text);
60 }
61
62 function hide() {
63 lockscreen.hide();
64 coverPage.hide();
65 }
66
67 function notifyAuthenticationSucceeded() {
68 lockscreen.hide();
69 }
70
71 function notifyAuthenticationFailed() {
72 lockscreen.clear(true);
73 }
74
75 function reset() {
76 coverPage.show();
77 }
78
79 function tryToUnlock(toTheRight) {
80 var coverChanged = coverPage.shown;
81 lockscreen.maybeShow();
82 if (toTheRight) {
83 coverPage.hideRight();
84 } else {
85 coverPage.hide();
86 }
87 return coverChanged;
88 }
89
90 onLockedChanged: {
91 if (locked) {
92 lockscreen.maybeShow();
93 } else {
94 lockscreen.hide();
95 }
96 }
97
98 Lockscreen {
99 id: lockscreen
100 objectName: "lockscreen"
101
102 shown: false
103 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
104 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
105 anchors.fill: parent
106 visible: required
107 enabled: !coverPage.shown
108 background: root.background
109 darkenBackground: 0.4
110 alphaNumeric: root.alphanumeric
111 minPinLength: 4
112 maxPinLength: 4
113
114 property string promptText
115 infoText: promptText !== "" ? i18n.tr("Enter %1").arg(promptText) :
116 alphaNumeric ? i18n.tr("Enter passphrase") :
117 i18n.tr("Enter passcode")
118 errorText: promptText !== "" ? i18n.tr("Sorry, incorrect %1").arg(promptText) :
119 alphaNumeric ? i18n.tr("Sorry, incorrect passphrase") + "\n" +
120 i18n.tr("Please re-enter") :
121 i18n.tr("Sorry, incorrect passcode")
122
123 onEntered: root.responded(passphrase)
124 onCancel: coverPage.show()
125 onEmergencyCall: root.emergencyCall()
126
127 function maybeShow() {
128 if (root.locked && !shown) {
129 showNow();
130 }
131 }
132 }
133
134 Rectangle {
135 anchors.fill: parent
136 color: "black"
137 opacity: coverPage.showProgress * 0.8
138 }
139
140 CoverPage {
141 id: coverPage
142 objectName: "coverPage"
143 height: parent.height
144 width: parent.width
145 background: root.background
146 onTease: root.tease()
147
148 onShowProgressChanged: {
149 if (showProgress === 1) {
150 lockscreen.reset();
151 }
152
153 if (showProgress === 0) {
154 if (root.locked) {
155 lockscreen.clear(false); // to reset focus if necessary
156 } else {
157 root.responded("");
158 }
159 }
160 }
161
162 Clock {
163 anchors {
164 top: parent.top
165 topMargin: units.gu(2)
166 horizontalCenter: parent.horizontalCenter
167 }
168 }
169 }
170}
0171
=== added file 'qml/Greeter/WideView.qml'
--- qml/Greeter/WideView.qml 1970-01-01 00:00:00 +0000
+++ qml/Greeter/WideView.qml 2015-02-23 15:44:00 +0000
@@ -0,0 +1,134 @@
1/*
2 * Copyright (C) 2015 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.3
18import Ubuntu.Components 1.1
19
20FocusScope {
21 id: root
22
23 property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
24 property alias launcherOffset: coverPage.launcherOffset
25 property alias currentIndex: loginList.currentIndex
26 property int delayMinutes // TODO
27 property alias backgroundTopMargin: coverPage.backgroundTopMargin
28 property alias background: coverPage.background
29 property bool locked
30 property bool alphanumeric // unused
31 property alias userModel: loginList.model
32 property alias infographicModel: coverPage.infographicModel
33 readonly property bool fullyShown: coverPage.showProgress === 1
34 readonly property bool required: coverPage.required
35
36 // so that it can be replaced in tests with a mock object
37 property var inputMethod: Qt.inputMethod
38
39 signal selected(int index)
40 signal responded(string response)
41 signal tease()
42 signal emergencyCall() // unused
43
44 function showMessage(html) {
45 loginList.showMessage(html);
46 }
47
48 function showPrompt(text, isSecret, isDefaultPrompt) {
49 loginList.showPrompt(text, isSecret, isDefaultPrompt);
50 }
51
52 function showLastChance() {
53 // TODO
54 }
55
56 function hide() {
57 coverPage.hide();
58 }
59
60 function notifyAuthenticationSucceeded() {
61 // Nothing needed
62 }
63
64 function notifyAuthenticationFailed() {
65 loginList.showError();
66 }
67
68 function reset() {
69 loginList.reset();
70 }
71
72 function tryToUnlock(toTheRight) {
73 if (root.locked) {
74 coverPage.show();
75 loginList.tryToUnlock();
76 return false;
77 } else {
78 var coverChanged = coverPage.shown;
79 if (toTheRight) {
80 coverPage.hideRight();
81 } else {
82 coverPage.hide();
83 }
84 return coverChanged;
85 }
86 }
87
88 Rectangle {
89 anchors.fill: parent
90 color: "black"
91 opacity: coverPage.showProgress * 0.8
92 }
93
94 CoverPage {
95 id: coverPage
96 objectName: "coverPage"
97 height: parent.height
98 width: parent.width
99 draggable: !root.locked
100
101 infographics {
102 height: 0.75 * parent.height
103 anchors.leftMargin: loginList.x + loginList.width
104 }
105
106 onTease: root.tease()
107
108 onShowProgressChanged: {
109 if (showProgress === 0 && !root.locked) {
110 root.responded("");
111 }
112 }
113
114 LoginList {
115 id: loginList
116 objectName: "loginList"
117
118 anchors {
119 left: parent.left
120 leftMargin: Math.min(parent.width * 0.16, units.gu(20))
121 top: parent.top
122 }
123 width: units.gu(29)
124 height: inputMethod && inputMethod.visible ? parent.height - inputMethod.keyboardRectangle.height
125 : parent.height
126 Behavior on height { UbuntuNumberAnimation {} }
127
128 locked: root.locked
129
130 onSelected: root.selected(index)
131 onResponded: root.responded(response)
132 }
133 }
134}
0135
=== modified file 'qml/Shell.qml'
--- qml/Shell.qml 2015-02-11 17:12:49 +0000
+++ qml/Shell.qml 2015-02-23 15:44:00 +0000
@@ -22,9 +22,7 @@
22import Ubuntu.Components 0.122import Ubuntu.Components 0.1
23import Ubuntu.Components.Popups 1.023import Ubuntu.Components.Popups 1.0
24import Ubuntu.Gestures 0.124import Ubuntu.Gestures 0.1
25import Ubuntu.SystemImage 0.1
26import Ubuntu.Telephony 0.1 as Telephony25import Ubuntu.Telephony 0.1 as Telephony
27import Unity.Connectivity 0.1
28import Unity.Launcher 0.126import Unity.Launcher 0.1
29import Utils 0.127import Utils 0.1
30import LightDM 0.1 as LightDM28import LightDM 0.1 as LightDM
@@ -46,9 +44,9 @@
46Item {44Item {
47 id: shell45 id: shell
4846
49 // Disable everything so that user can't swipe greeter or launcher until47 // Disable everything while greeter is waiting, so that the user can't swipe
50 // we get first prompt/authenticate, which will re-enable the shell.48 // the greeter or launcher until we know whether the session is locked.
51 enabled: false49 enabled: !greeter.waiting
5250
53 // this is only here to select the width / height of the window if not running fullscreen51 // this is only here to select the width / height of the window if not running fullscreen
54 property bool tablet: false52 property bool tablet: false
@@ -61,18 +59,9 @@
61 : gsImageTester.status == Image.Ready ? gsImageTester.source : defaultBackground59 : gsImageTester.status == Image.Ready ? gsImageTester.source : defaultBackground
62 readonly property real panelHeight: panel.panelHeight60 readonly property real panelHeight: panel.panelHeight
6361
64 readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated && !forcedUnlock
65 readonly property alias hasLockedApp: greeter.hasLockedApp
66 readonly property bool forcedUnlock: tutorial.running
67 onForcedUnlockChanged: if (forcedUnlock) lockscreen.hide()
68
69 property bool sideStageEnabled: shell.width >= units.gu(100)62 property bool sideStageEnabled: shell.width >= units.gu(100)
70 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId63 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
7164
72 property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
73 property int failedLoginsDelayAttempts: 7 // number of failed logins
74 property int failedLoginsDelayMinutes: 5 // minutes of forced waiting
75
76 property int orientation65 property int orientation
77 readonly property int deviceOrientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)66 readonly property int deviceOrientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
78 onDeviceOrientationAngleChanged: {67 onDeviceOrientationAngleChanged: {
@@ -99,7 +88,7 @@
99 }88 }
10089
101 function startLockedApp(app) {90 function startLockedApp(app) {
102 if (shell.locked) {91 if (greeter.locked) {
103 greeter.lockedApp = app;92 greeter.lockedApp = app;
104 }93 }
105 shell.activateApplication(app);94 shell.activateApplication(app);
@@ -117,6 +106,7 @@
117106
118 GSettings {107 GSettings {
119 id: backgroundSettings108 id: backgroundSettings
109 objectName: "backgroundSettings"
120 schema.id: "org.gnome.desktop.background"110 schema.id: "org.gnome.desktop.background"
121 }111 }
122112
@@ -222,53 +212,32 @@
222212
223 Connections {213 Connections {
224 target: ApplicationManager214 target: ApplicationManager
225 onFocusRequested: {
226 if (greeter.narrowMode) {
227 if (appId === "dialer-app" && callManager.hasCalls && shell.locked) {
228 // If we are in the middle of a call, make dialer lockedApp and show it.
229 // This can happen if user backs out of dialer back to greeter, then
230 // launches dialer again.
231 greeter.lockedApp = appId;
232 }
233 if (greeter.hasLockedApp) {
234 if (appId === greeter.lockedApp) {
235 lockscreen.hide() // show locked app
236 } else {
237 greeter.startUnlock() // show lockscreen if necessary
238 }
239 }
240 greeter.hide();
241 } else {
242 if (LightDM.Greeter.active) {
243 greeter.startUnlock()
244 }
245 }
246 }
247215
216 // This signal is also fired when we try to focus the current app
217 // again. We rely on this!
248 onFocusedApplicationIdChanged: {218 onFocusedApplicationIdChanged: {
249 if (greeter.hasLockedApp && greeter.lockedApp !== ApplicationManager.focusedApplicationId) {219 var appId = ApplicationManager.focusedApplicationId;
250 greeter.startUnlock()220
251 }221 if (tutorial.running && appId != "unity8-dash") {
252 panel.indicators.hide();
253 }
254
255 onApplicationAdded: {
256 if (appId != "unity8-dash") {
257 if (greeter.shown) {
258 greeter.startUnlock();
259 }
260
261 // If this happens on first boot, we may be in edge222 // If this happens on first boot, we may be in edge
262 // tutorial or wizard while receiving a call. But a call223 // tutorial or wizard while receiving a call. But a call
263 // is more important than wizard so just bail out of those.224 // is more important than wizard so just bail out of those.
264 if (tutorial.running) {225 tutorial.finish();
265 tutorial.finish();226 wizard.hide();
266 wizard.hide();227 }
267 }228
268 }229 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
269 if (greeter.narrowMode && greeter.hasLockedApp && appId === greeter.lockedApp) {230 // If we are in the middle of a call, make dialer lockedApp and show it.
270 lockscreen.hide() // show locked app231 // This can happen if user backs out of dialer back to greeter, then
271 }232 // launches dialer again.
233 greeter.lockedApp = appId;
234 }
235 greeter.notifyAppFocused(appId);
236
237 panel.indicators.hide();
238 }
239
240 onApplicationAdded: {
272 launcher.hide();241 launcher.hide();
273 }242 }
274 }243 }
@@ -292,7 +261,6 @@
292261
293 property bool interactive: tutorial.spreadEnabled262 property bool interactive: tutorial.spreadEnabled
294 && !greeter.shown263 && !greeter.shown
295 && !lockscreen.shown
296 && panel.indicators.fullyClosed264 && panel.indicators.fullyClosed
297 && launcher.progress == 0265 && launcher.progress == 0
298 && !notifications.useModal266 && !notifications.useModal
@@ -328,7 +296,7 @@
328 Binding {296 Binding {
329 target: applicationsDisplayLoader.item297 target: applicationsDisplayLoader.item
330 property: "inverseProgress"298 property: "inverseProgress"
331 value: launcher.progress299 value: greeter.locked ? 0 : launcher.progress
332 }300 }
333 Binding {301 Binding {
334 target: applicationsDisplayLoader.item302 target: applicationsDisplayLoader.item
@@ -379,299 +347,50 @@
379 }347 }
380 }348 }
381349
382 Lockscreen {350 Greeter {
383 id: lockscreen351 id: greeter
384 objectName: "lockscreen"352 objectName: "greeter"
385353
386 hides: [launcher, panel.indicators]354 hides: [launcher, panel.indicators]
387 shown: false355 tabletMode: shell.sideStageEnabled
388 enabled: true356 launcherOffset: launcher.progress
389 showAnimation: StandardAnimation { property: "opacity"; to: 1 }357 forcedUnlock: tutorial.running
390 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
391 y: panel.panelHeight
392 visible: required
393 width: parent.width
394 height: parent.height - panel.panelHeight
395 background: shell.background358 background: shell.background
396 darkenBackground: 0.4359
397 alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard360 anchors.fill: parent
398 minPinLength: 4361 anchors.topMargin: panel.panelHeight
399 maxPinLength: 4362
400363 // avoid overlapping with Launcher's edge drag area
401 property string promptText364 // FIXME: Fix TouchRegistry & friends and remove this workaround
402 infoText: promptText !== "" ? i18n.tr("Enter %1").arg(promptText) :365 // Issue involves launcher's DDA getting disabled on a long
403 alphaNumeric ? i18n.tr("Enter passphrase") :366 // left-edge drag
404 i18n.tr("Enter passcode")367 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
405 errorText: promptText !== "" ? i18n.tr("Sorry, incorrect %1").arg(promptText) :368
406 alphaNumeric ? i18n.tr("Sorry, incorrect passphrase") + "\n" +369 onSessionStarted: {
407 i18n.tr("Please re-enter") :370 launcher.hide();
408 i18n.tr("Sorry, incorrect passcode")371 }
409372
410 // FIXME: We *should* show emergency dialer if there is a SIM present,373 onTease: {
411 // regardless of whether the side stage is enabled. But right now,374 if (!tutorial.running) {
412 // the assumption is that narrow screens are phones which have SIMs375 launcher.tease();
413 // and wider screens are tablets which don't. When we do allow this376 }
414 // on devices with a side stage and a SIM, work should be done to377 }
415 // ensure that the main stage is disabled while the dialer is present378
416 // in the side stage. See the FIXME in the stage loader in this file.
417 showEmergencyCallButton: !shell.sideStageEnabled
418
419 onEntered: LightDM.Greeter.respond(passphrase);
420 onCancel: greeter.show()
421 onEmergencyCall: startLockedApp("dialer-app")379 onEmergencyCall: startLockedApp("dialer-app")
422380
423 onShownChanged: if (shown) greeter.lockedApp = ""
424
425 function maybeShow() {
426 if (!shell.forcedUnlock && !shown) {
427 showNow();
428 }
429 }
430
431 Timer {381 Timer {
432 id: forcedDelayTimer382 // See powerConnection for why this is useful
433 interval: 1000 * 60383 id: showGreeterDelayed
384 interval: 1
434 onTriggered: {385 onTriggered: {
435 if (lockscreen.delayMinutes > 0) {386 greeter.forceShow();
436 lockscreen.delayMinutes -= 1387 }
437 if (lockscreen.delayMinutes > 0) {388 }
438 start() // go again389
439 }390 Binding {
440 }391 target: ApplicationManager
441 }392 property: "suspended"
442 }393 value: greeter.shown
443
444 Component.onCompleted: {
445 if (greeter.narrowMode) {
446 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
447 }
448 }
449 }
450
451 Connections {
452 target: LightDM.Greeter
453
454 onShowGreeter: greeter.show()
455 onHideGreeter: greeter.login()
456
457 onShowPrompt: {
458 shell.enabled = true;
459 if (!LightDM.Greeter.active) {
460 return; // could happen if hideGreeter() comes in before we prompt
461 }
462 if (greeter.narrowMode) {
463 lockscreen.promptText = isDefaultPrompt ? "" : text.toLowerCase();
464 lockscreen.maybeShow();
465 }
466 }
467
468 onPromptlessChanged: {
469 if (!LightDM.Greeter.active) {
470 return; // could happen if hideGreeter() comes in before we prompt
471 }
472 if (greeter.narrowMode) {
473 if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
474 lockscreen.hide()
475 } else {
476 lockscreen.reset();
477 lockscreen.maybeShow();
478 }
479 }
480 }
481
482 onAuthenticationComplete: {
483 shell.enabled = true;
484 if (LightDM.Greeter.authenticated) {
485 AccountsService.failedLogins = 0
486 }
487 // Else only penalize user for a failed login if they actually were
488 // prompted for a password. We do this below after the promptless
489 // early exit.
490
491 if (LightDM.Greeter.promptless) {
492 return;
493 }
494
495 if (LightDM.Greeter.authenticated) {
496 greeter.login();
497 } else {
498 AccountsService.failedLogins++
499 if (maxFailedLogins >= 2) { // require at least a warning
500 if (AccountsService.failedLogins === maxFailedLogins - 1) {
501 var title = lockscreen.alphaNumeric ?
502 i18n.tr("Sorry, incorrect passphrase.") :
503 i18n.tr("Sorry, incorrect passcode.")
504 var text = i18n.tr("This will be your last attempt.") + " " +
505 (lockscreen.alphaNumeric ?
506 i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
507 i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
508 lockscreen.showInfoPopup(title, text)
509 } else if (AccountsService.failedLogins >= maxFailedLogins) {
510 SystemImage.factoryReset() // Ouch!
511 }
512 }
513 if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
514 lockscreen.delayMinutes = failedLoginsDelayMinutes
515 forcedDelayTimer.start()
516 }
517
518 lockscreen.clear(true);
519 if (greeter.narrowMode) {
520 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
521 }
522 }
523 }
524 }
525
526 Binding {
527 target: LightDM.Greeter
528 property: "active"
529 value: greeter.shown || lockscreen.shown || greeter.hasLockedApp
530 }
531
532 Rectangle {
533 anchors.fill: parent
534 color: "black"
535 opacity: greeterWrapper.showProgress * 0.8
536 }
537
538 Item {
539 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
540 id: greeterWrapper
541 objectName: "greeterWrapper"
542 x: (greeter.narrowMode && greeter.showProgress > 0) ? launcher.progress : 0
543 y: panel.panelHeight
544 width: parent.width
545 height: parent.height - panel.panelHeight
546
547 Behavior on x {
548 enabled: !launcher.dashSwipe
549 StandardAnimation {}
550 }
551
552 property bool fullyShown: showProgress === 1.0
553 onFullyShownChanged: {
554 // Wait until the greeter is completely covering lockscreen before resetting it.
555 if (greeter.narrowMode && fullyShown && !LightDM.Greeter.authenticated) {
556 lockscreen.reset();
557 lockscreen.maybeShow();
558 }
559 }
560
561 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
562 onShowProgressChanged: {
563 if (showProgress === 0) {
564 if ((LightDM.Greeter.promptless && LightDM.Greeter.authenticated) || shell.forcedUnlock) {
565 greeter.login()
566 } else if (greeter.narrowMode) {
567 lockscreen.clear(false) // to reset focus if necessary
568 }
569 }
570 }
571
572 Greeter {
573 id: greeter
574 objectName: "greeter"
575
576 signal sessionStarted() // helpful for tests
577
578 property string lockedApp: ""
579 property bool hasLockedApp: lockedApp !== ""
580
581 hides: [launcher, panel.indicators]
582 loadContent: required || lockscreen.required // keeps content in memory for quick show()
583
584 locked: shell.locked
585
586 background: shell.background
587
588 width: parent.width
589 height: parent.height
590
591
592 // avoid overlapping with Launcher's edge drag area
593 // FIXME: Fix TouchRegistry & friends and remove this workaround
594 // Issue involves launcher's DDA getting disabled on a long
595 // left-edge drag
596 dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
597
598 function startUnlock() {
599 if (narrowMode) {
600 if (!LightDM.Greeter.authenticated) {
601 lockscreen.maybeShow()
602 }
603 hide()
604 } else {
605 show()
606 tryToUnlock()
607 }
608 }
609
610 function login() {
611 enabled = false;
612 if (LightDM.Greeter.startSessionSync()) {
613 sessionStarted();
614 greeter.hide();
615 lockscreen.hide();
616 launcher.hide();
617 }
618 enabled = true;
619 }
620
621 Timer {
622 // See powerConnection for why this is useful
623 id: showGreeterDelayed
624 interval: 1
625 onTriggered: {
626 greeter.showNow();
627 }
628 }
629
630 onShownChanged: {
631 if (shown) {
632 // Disable everything so that user can't swipe greeter or
633 // launcher until we get the next prompt/authenticate, which
634 // will re-enable the shell.
635 shell.enabled = false;
636
637 if (greeter.narrowMode) {
638 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
639 } else {
640 reset()
641 }
642 greeter.lockedApp = "";
643 greeter.forceActiveFocus();
644 }
645 }
646
647 Component.onCompleted: {
648 Connectivity.unlockAllModems()
649 }
650
651 onUnlocked: greeter.hide()
652 onSelected: {
653 // Update launcher items for new user
654 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
655 AccountsService.user = user;
656 LauncherModel.setUser(user);
657 }
658
659 onTapped: {
660 if (!tutorial.running) {
661 launcher.tease();
662 }
663 }
664 onDraggingChanged: {
665 if (dragging && !tutorial.running) {
666 launcher.tease();
667 }
668 }
669
670 Binding {
671 target: ApplicationManager
672 property: "suspended"
673 value: (greeter.shown && greeterWrapper.showProgress == 1) || lockscreen.shown
674 }
675 }394 }
676 }395 }
677396
@@ -680,7 +399,7 @@
680 target: callManager399 target: callManager
681400
682 onHasCallsChanged: {401 onHasCallsChanged: {
683 if (shell.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {402 if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
684 // We just received an incoming call while locked. The403 // We just received an incoming call while locked. The
685 // indicator will have already launched dialer-app for us, but404 // indicator will have already launched dialer-app for us, but
686 // there is a race between "hasCalls" changing and the dialer405 // there is a race between "hasCalls" changing and the dialer
@@ -720,9 +439,7 @@
720 return439 return
721 }440 }
722441
723 if (LightDM.Greeter.active) {442 greeter.notifyAboutToFocusApp("unity8-dash");
724 greeter.startUnlock()
725 }
726443
727 var animate = !LightDM.Greeter.active && !stages.shown444 var animate = !LightDM.Greeter.active && !stages.shown
728 dash.setCurrentScope(0, animate, false)445 dash.setCurrentScope(0, animate, false)
@@ -730,17 +447,11 @@
730 }447 }
731448
732 function showDash() {449 function showDash() {
733 if (greeter.hasLockedApp || // just in case user gets here450 if (greeter.notifyShowingDashFromDrag()) {
734 (!greeter.narrowMode && shell.locked)) {
735 return
736 }
737
738 if (greeter.shown) {
739 greeter.hideRight();
740 launcher.fadeOut();451 launcher.fadeOut();
741 }452 }
742453
743 if (ApplicationManager.focusedApplicationId != "unity8-dash") {454 if (!greeter.locked && ApplicationManager.focusedApplicationId != "unity8-dash") {
744 ApplicationManager.requestFocusApplication("unity8-dash")455 ApplicationManager.requestFocusApplication("unity8-dash")
745 launcher.fadeOut();456 launcher.fadeOut();
746 }457 }
@@ -758,7 +469,9 @@
758 anchors.fill: parent //because this draws indicator menus469 anchors.fill: parent //because this draws indicator menus
759 indicators {470 indicators {
760 hides: [launcher]471 hides: [launcher]
761 available: tutorial.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp472 available: tutorial.panelEnabled
473 && (!greeter.locked || AccountsService.enableIndicatorsWhileLocked)
474 && !greeter.hasLockedApp
762 contentEnabled: tutorial.panelContentEnabled475 contentEnabled: tutorial.panelContentEnabled
763 width: parent.width > units.gu(60) ? units.gu(40) : parent.width476 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
764477
@@ -772,7 +485,7 @@
772 }485 }
773 }486 }
774 callHint {487 callHint {
775 greeterShown: greeter.shown || lockscreen.shown488 greeterShown: greeter.shown
776 }489 }
777490
778 property bool topmostApplicationIsFullscreen:491 property bool topmostApplicationIsFullscreen:
@@ -794,7 +507,9 @@
794 anchors.bottom: parent.bottom507 anchors.bottom: parent.bottom
795 width: parent.width508 width: parent.width
796 dragAreaWidth: shell.edgeSize509 dragAreaWidth: shell.edgeSize
797 available: tutorial.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp510 available: tutorial.launcherEnabled
511 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
512 && !greeter.hasLockedApp
798 inverted: usageModeSettings.usageMode === "Staged"513 inverted: usageModeSettings.usageMode === "Staged"
799 shadeBackground: !tutorial.running514 shadeBackground: !tutorial.running
800515
@@ -806,11 +521,10 @@
806 }521 }
807 }522 }
808 onLauncherApplicationSelected: {523 onLauncherApplicationSelected: {
809 if (greeter.hasLockedApp) {524 if (!tutorial.running) {
810 greeter.startUnlock()525 greeter.notifyAboutToFocusApp(appId);
811 }
812 if (!tutorial.running)
813 shell.activateApplication(appId)526 shell.activateApplication(appId)
527 }
814 }528 }
815 onShownChanged: {529 onShownChanged: {
816 if (shown) {530 if (shown) {
817531
=== modified file 'tests/autopilot/unity8/shell/emulators/greeter.py'
--- tests/autopilot/unity8/shell/emulators/greeter.py 2014-09-08 10:33:50 +0000
+++ tests/autopilot/unity8/shell/emulators/greeter.py 2015-02-23 15:44:00 +0000
@@ -19,6 +19,7 @@
1919
20import ubuntuuitoolkit20import ubuntuuitoolkit
2121
22from autopilot.utilities import sleep
22from unity8.shell.emulators import UnityEmulatorBase23from unity8.shell.emulators import UnityEmulatorBase
2324
2425
@@ -26,12 +27,27 @@
26 """An emulator that understands the greeter screen."""27 """An emulator that understands the greeter screen."""
2728
28 def wait_swiped_away(self):29 def wait_swiped_away(self):
29 self.showProgress.wait_for(0)30 # We have to be careful here, because coverPage can go away at any time
31 # if there isn't a lockscreen behind it (it hides completely, then
32 # the greeter disposes it). But if there *is* a lockscreen, then we
33 # need a different check, by looking at its showProgress. So make our
34 # own wait_for loop and check both possibilities.
35 for i in range(10):
36 if not self.required:
37 return
38 coverPage = self.select_single(objectName='coverPage')
39 if coverPage.showProgress == 0:
40 return
41 sleep(1)
42
43 raise AssertionError("Greeter cover page still up after 10s")
44
3045
31 def swipe(self):46 def swipe(self):
32 """Swipe the greeter screen away."""47 """Swipe the greeter screen away."""
33 self.created.wait_for(True)48 self.waiting.wait_for(False)
34 self.showProgress.wait_for(1)49 coverPage = self.select_single(objectName='coverPage')
50 coverPage.showProgress.wait_for(1)
3551
36 rect = self.globalRect52 rect = self.globalRect
37 start_x = rect[0] + rect[2] - 353 start_x = rect[0] + rect[2] - 3
3854
=== modified file 'tests/autopilot/unity8/shell/emulators/main_window.py'
--- tests/autopilot/unity8/shell/emulators/main_window.py 2015-01-21 04:11:36 +0000
+++ tests/autopilot/unity8/shell/emulators/main_window.py 2015-02-23 15:44:00 +0000
@@ -35,12 +35,6 @@
35 def get_greeter(self):35 def get_greeter(self):
36 return self.select_single(Greeter)36 return self.select_single(Greeter)
3737
38 def get_greeter_content_loader(self):
39 return self.wait_select_single(
40 "QQuickLoader",
41 objectName="greeterContentLoader"
42 )
43
44 def get_login_loader(self):38 def get_login_loader(self):
45 return self.select_single("QQuickLoader", objectName="loginLoader")39 return self.select_single("QQuickLoader", objectName="loginLoader")
4640
4741
=== modified file 'tests/autopilot/unity8/shell/tests/__init__.py'
--- tests/autopilot/unity8/shell/tests/__init__.py 2015-01-21 13:50:14 +0000
+++ tests/autopilot/unity8/shell/tests/__init__.py 2015-02-23 15:44:00 +0000
@@ -375,9 +375,8 @@
375 self._proxy = None375 self._proxy = None
376376
377 def wait_for_unity(self):377 def wait_for_unity(self):
378 greeter_content_loader = self.main_window.wait_select_single(378 greeter = self.main_window.wait_select_single(objectName='greeter')
379 objectName='greeterContentLoader')379 greeter.waiting.wait_for(False)
380 greeter_content_loader.progress.wait_for(1)
381380
382 def get_dash(self):381 def get_dash(self):
383 pid = process_helpers.get_job_pid('unity8-dash')382 pid = process_helpers.get_job_pid('unity8-dash')
384383
=== modified file 'tests/autopilot/unity8/shell/tests/test_lock_screen.py'
--- tests/autopilot/unity8/shell/tests/test_lock_screen.py 2015-01-21 13:50:14 +0000
+++ tests/autopilot/unity8/shell/tests/test_lock_screen.py 2015-02-23 15:44:00 +0000
@@ -47,14 +47,13 @@
47 self.launch_unity()47 self.launch_unity()
48 greeter = self.main_window.get_greeter()48 greeter = self.main_window.get_greeter()
4949
50 if greeter.narrowMode:50 if not greeter.tabletMode:
51 greeter.swipe()51 greeter.swipe()
52 lockscreen = self._wait_for_lockscreen()52 lockscreen = self._wait_for_lockscreen()
53 self.main_window.enter_pin_code("1234")53 self.main_window.enter_pin_code("1234")
54 self.assertThat(lockscreen.shown, Eventually(Equals(False)))
55 else:54 else:
56 self._enter_prompt_passphrase("1234\n")55 self._enter_prompt_passphrase("1234\n")
57 self.assertThat(greeter.shown, Eventually(Equals(False)))56 self.assertThat(greeter.shown, Eventually(Equals(False)))
5857
59 def test_can_unlock_passphrase_screen(self):58 def test_can_unlock_passphrase_screen(self):
60 """Must be able to unlock the passphrase entry screen."""59 """Must be able to unlock the passphrase entry screen."""
@@ -63,14 +62,13 @@
63 self.launch_unity()62 self.launch_unity()
64 greeter = self.main_window.get_greeter()63 greeter = self.main_window.get_greeter()
6564
66 if greeter.narrowMode:65 if not greeter.tabletMode:
67 greeter.swipe()66 greeter.swipe()
68 lockscreen = self._wait_for_lockscreen()67 lockscreen = self._wait_for_lockscreen()
69 self._enter_pin_passphrase("password")68 self._enter_pin_passphrase("password")
70 self.assertThat(lockscreen.shown, Eventually(Equals(False)))
71 else:69 else:
72 self._enter_prompt_passphrase("password")70 self._enter_prompt_passphrase("password")
73 self.assertThat(greeter.shown, Eventually(Equals(False)))71 self.assertThat(greeter.shown, Eventually(Equals(False)))
7472
75 def test_pin_screen_wrong_code(self):73 def test_pin_screen_wrong_code(self):
76 """Entering the wrong pin code must not dismiss the lock screen."""74 """Entering the wrong pin code must not dismiss the lock screen."""
@@ -78,18 +76,17 @@
78 self.launch_unity()76 self.launch_unity()
79 greeter = self.main_window.get_greeter()77 greeter = self.main_window.get_greeter()
8078
81 if greeter.narrowMode:79 if not greeter.tabletMode:
82 greeter.swipe()80 greeter.swipe()
83 lockscreen = self._wait_for_lockscreen()81 lockscreen = self._wait_for_lockscreen()
84 self.main_window.enter_pin_code("4321")82 self.main_window.enter_pin_code("4321")
85 pinentryField = self.main_window.get_pinentryField()83 pinentryField = self.main_window.get_pinentryField()
86 self.assertThat(pinentryField.text, Eventually(Equals("")))84 self.assertThat(pinentryField.text, Eventually(Equals("")))
87 self.assertThat(lockscreen.shown, Eventually(Equals(True)))
88 else:85 else:
89 self._enter_prompt_passphrase("4231\n")86 self._enter_prompt_passphrase("4231\n")
90 prompt = self.main_window.get_greeter().get_prompt()87 prompt = self.main_window.get_greeter().get_prompt()
91 self.assertThat(prompt.text, Eventually(Equals("")))88 self.assertThat(prompt.text, Eventually(Equals("")))
92 self.assertThat(greeter.shown, Eventually(Equals(True)))89 self.assertThat(greeter.shown, Eventually(Equals(True)))
9390
94 def test_passphrase_screen_wrong_password(self):91 def test_passphrase_screen_wrong_password(self):
95 """Entering the wrong password must not dismiss the lock screen."""92 """Entering the wrong password must not dismiss the lock screen."""
@@ -97,18 +94,17 @@
97 self.launch_unity()94 self.launch_unity()
98 greeter = self.main_window.get_greeter()95 greeter = self.main_window.get_greeter()
9996
100 if greeter.narrowMode:97 if not greeter.tabletMode:
101 greeter.swipe()98 greeter.swipe()
102 lockscreen = self._wait_for_lockscreen()99 lockscreen = self._wait_for_lockscreen()
103 self._enter_pin_passphrase("foobar")100 self._enter_pin_passphrase("foobar")
104 pinentryField = self.main_window.get_pinentryField()101 pinentryField = self.main_window.get_pinentryField()
105 self.assertThat(pinentryField.text, Eventually(Equals("")))102 self.assertThat(pinentryField.text, Eventually(Equals("")))
106 self.assertThat(lockscreen.shown, Eventually(Equals(True)))
107 else:103 else:
108 self._enter_prompt_passphrase("foobar")104 self._enter_prompt_passphrase("foobar")
109 prompt = self.main_window.get_greeter().get_prompt()105 prompt = self.main_window.get_greeter().get_prompt()
110 self.assertThat(prompt.text, Eventually(Equals("")))106 self.assertThat(prompt.text, Eventually(Equals("")))
111 self.assertThat(greeter.shown, Eventually(Equals(True)))107 self.assertThat(greeter.shown, Eventually(Equals(True)))
112108
113 def _wait_for_lockscreen(self):109 def _wait_for_lockscreen(self):
114 """Wait for the lock screen to load, and return it."""110 """Wait for the lock screen to load, and return it."""
115111
=== modified file 'tests/plugins/LightDM/CMakeLists.txt'
--- tests/plugins/LightDM/CMakeLists.txt 2015-01-27 15:50:02 +0000
+++ tests/plugins/LightDM/CMakeLists.txt 2015-02-23 15:44:00 +0000
@@ -4,17 +4,32 @@
4 )4 )
5qt5_use_modules(GreeterDBusTestExec Core DBus Quick Test)5qt5_use_modules(GreeterDBusTestExec Core DBus Quick Test)
66
7add_executable(GreeterUsersModelTestExec
8 usersmodel.cpp
9 ${CMAKE_SOURCE_DIR}/plugins/LightDM/UsersModel.cpp
10 ${CMAKE_SOURCE_DIR}/plugins/Utils/unitysortfilterproxymodelqml.cpp
11 )
12qt5_use_modules(GreeterUsersModelTestExec Core Test)
13
7include_directories(14include_directories(
8 ${CMAKE_CURRENT_BINARY_DIR}15 ${CMAKE_CURRENT_BINARY_DIR}
9 ${CMAKE_SOURCE_DIR}/plugins/LightDM16 ${CMAKE_SOURCE_DIR}/plugins/LightDM
17 ${CMAKE_SOURCE_DIR}/plugins/Utils
10 ${CMAKE_SOURCE_DIR}/tests/mocks/LightDM18 ${CMAKE_SOURCE_DIR}/tests/mocks/LightDM
11 ${LIBLIGHTDM_INCLUDE_DIRS}19 )
12 )20
1321target_link_libraries(GreeterDBusTestExec
14add_dependencies(GreeterDBusTestExec MockLightDM)22 -L${CMAKE_BINARY_DIR}/tests/mocks/LightDM/liblightdm
15target_link_libraries(GreeterDBusTestExec -L${CMAKE_BINARY_DIR}/tests/mocks/LightDM/liblightdm23 -llightdm-qt5-2
16 -llightdm-qt5-2)24 )
25
26target_link_libraries(GreeterUsersModelTestExec
27 -L${CMAKE_BINARY_DIR}/tests/mocks/LightDM/liblightdm
28 -llightdm-qt5-2
29 )
1730
18add_definitions(-DCURRENT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")31add_definitions(-DCURRENT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
1932
20add_binary_qml_test(GreeterDBus "${CMAKE_BINARY_DIR}/tests/mocks/LightDM/liblightdm" MockLightDM "QML2_IMPORT_PATH=${CMAKE_BINARY_DIR}/tests/mocks")33add_binary_qml_test(GreeterDBus "${CMAKE_BINARY_DIR}/tests/mocks/LightDM/liblightdm" MockLightDM "QML2_IMPORT_PATH=${CMAKE_BINARY_DIR}/tests/mocks")
34
35add_binary_qml_test(GreeterUsersModel "${CMAKE_BINARY_DIR}/tests/mocks/LightDM/liblightdm" MockLightDM "LIBLIGHTDM_MOCK_MODE=full")
2136
=== added file 'tests/plugins/LightDM/usersmodel.cpp'
--- tests/plugins/LightDM/usersmodel.cpp 1970-01-01 00:00:00 +0000
+++ tests/plugins/LightDM/usersmodel.cpp 2015-02-23 15:44:00 +0000
@@ -0,0 +1,84 @@
1/*
2 * Copyright (C) 2015 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "UsersModel.h"
18
19#include <QLightDM/UsersModel>
20#include <QtTest>
21
22class GreeterUsersModelTest : public QObject
23{
24 Q_OBJECT
25
26private:
27 static int findName(QAbstractItemModel *model, const QString &name)
28 {
29 for (int i = 0; i < model->rowCount(QModelIndex()); i++) {
30 if (model->data(model->index(i, 0), QLightDM::UsersModel::NameRole).toString() == name) {
31 return i;
32 }
33 }
34 return -1;
35 }
36
37 static QString getStringValue(QAbstractItemModel *model, const QString &name, int role)
38 {
39 int i = findName(model, name);
40 return model->data(model->index(i, 0), role).toString();
41 }
42
43private Q_SLOTS:
44
45 void init()
46 {
47 model = new UsersModel();
48 QVERIFY(model);
49 sourceModel = new QLightDM::UsersModel();
50 QVERIFY(sourceModel);
51 }
52
53 void cleanup()
54 {
55 delete model;
56 delete sourceModel;
57 }
58
59 void testMangleColor()
60 {
61 QString background = getStringValue(sourceModel, "color-background", QLightDM::UsersModel::BackgroundPathRole);
62 QVERIFY(background == "#dd4814");
63
64 background = getStringValue(model, "color-background", QLightDM::UsersModel::BackgroundPathRole);
65 QVERIFY(background == "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>");
66 }
67
68 void testMangleEmptyName()
69 {
70 QString name = getStringValue(sourceModel, "empty-name", QLightDM::UsersModel::RealNameRole);
71 QVERIFY(name == "");
72
73 name = getStringValue(model, "empty-name", QLightDM::UsersModel::RealNameRole);
74 QVERIFY(name == "empty-name");
75 }
76
77private:
78 UsersModel *model;
79 QLightDM::UsersModel *sourceModel;
80};
81
82QTEST_MAIN(GreeterUsersModelTest)
83
84#include "usersmodel.moc"
085
=== modified file 'tests/qmltests/CMakeLists.txt'
--- tests/qmltests/CMakeLists.txt 2015-02-11 17:11:41 +0000
+++ tests/qmltests/CMakeLists.txt 2015-02-23 15:44:00 +0000
@@ -60,10 +60,11 @@
60add_qml_test(Dash/ScopeSettings ScopeSettingNumber)60add_qml_test(Dash/ScopeSettings ScopeSettingNumber)
61add_qml_test(Dash/ScopeSettings ScopeSettingString)61add_qml_test(Dash/ScopeSettings ScopeSettingString)
62add_qml_test(Dash/ScopeSettings ScopeSettingsWidgetFactory)62add_qml_test(Dash/ScopeSettings ScopeSettingsWidgetFactory)
63add_qml_test(Greeter MultiGreeter)63add_qml_test(Greeter Clock)
64add_qml_test(Greeter SingleGreeter)64add_qml_test(Greeter Greeter)
65add_qml_test(Greeter Infographics)65add_qml_test(Greeter Infographics)
66add_qml_test(Greeter Clock)66add_qml_test(Greeter NarrowView)
67add_qml_test(Greeter WideView)
67add_qml_test(Launcher Launcher)68add_qml_test(Launcher Launcher)
68add_qml_test(Notifications Notifications)69add_qml_test(Notifications Notifications)
69add_qml_test(Notifications VisualSnapDecisionsQueue)70add_qml_test(Notifications VisualSnapDecisionsQueue)
7071
=== added file 'tests/qmltests/Greeter/TestView.qml'
--- tests/qmltests/Greeter/TestView.qml 1970-01-01 00:00:00 +0000
+++ tests/qmltests/Greeter/TestView.qml 2015-02-23 15:44:00 +0000
@@ -0,0 +1,99 @@
1/*
2 * Copyright (C) 2015 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.3
18import Ubuntu.Components 1.1
19import "../Components"
20
21Item {
22 objectName: "testView"
23
24 property real dragHandleLeftMargin
25 property real launcherOffset
26 property int currentIndex
27 property int delayMinutes
28 property real backgroundTopMargin
29 property url background
30 property bool locked
31 property bool alphanumeric
32 property var userModel
33 property var infographicModel
34 readonly property bool fullyShown: _fullyShown
35 readonly property bool required: _required
36
37 property bool _fullyShown: true
38 property bool _required: true
39
40 signal selected(int index)
41 signal responded(string response)
42 signal tease()
43 signal emergencyCall()
44
45 signal _showMessageCalled(string html)
46 signal _showPromptCalled(string text, bool isSecret, bool isDefaultPrompt)
47 signal _showLastChanceCalled()
48 signal _hideCalled()
49 signal _notifyAuthenticationSucceededCalled()
50 signal _notifyAuthenticationFailedCalled()
51 signal _resetCalled()
52 signal _tryToUnlockCalled(bool toTheRight)
53
54 function showMessage(html) {
55 _showMessageCalled(html);
56 }
57
58 function showPrompt(text, isSecret, isDefaultPrompt) {
59 _showPromptCalled(text, isSecret, isDefaultPrompt);
60 }
61
62 function showLastChance() {
63 _showLastChanceCalled();
64 }
65
66 function hide() {
67 _hideCalled();
68 _required = false;
69 _fullyShown = false;
70 }
71
72 function notifyAuthenticationSucceeded() {
73 _notifyAuthenticationSucceededCalled();
74 }
75
76 function notifyAuthenticationFailed() {
77 _notifyAuthenticationFailedCalled();
78 }
79
80 function reset() {
81 _resetCalled();
82 }
83
84 function tryToUnlock(toTheRight) {
85 _tryToUnlockCalled(toTheRight);
86 return true;
87 }
88
89 Rectangle {
90 anchors.fill: parent
91 color: "black"
92
93 Label {
94 text: "Fake view, nothing to see here"
95 color: "white"
96 anchors.centerIn: parent
97 }
98 }
99}
0100
=== added file 'tests/qmltests/Greeter/tst_Greeter.qml'
--- tests/qmltests/Greeter/tst_Greeter.qml 1970-01-01 00:00:00 +0000
+++ tests/qmltests/Greeter/tst_Greeter.qml 2015-02-23 15:44:00 +0000
@@ -0,0 +1,472 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.0
18import QtTest 1.0
19import ".."
20import "../../../qml/Greeter"
21import Ubuntu.Components 0.1
22import AccountsService 0.1
23import LightDM 0.1 as LightDM
24import Unity.Test 0.1 as UT
25
26Item {
27 width: units.gu(120)
28 height: units.gu(80)
29
30 property url defaultBackground: Qt.resolvedUrl("../../../qml/graphics/tablet_background.jpg")
31
32 Component.onCompleted: {
33 // set the mock mode before loading
34 LightDM.Greeter.mockMode = "full";
35 LightDM.Users.mockMode = "full";
36 loader.active = true;
37 }
38
39 Loader {
40 id: loader
41
42 active: false
43 anchors.fill: parent
44
45 property bool itemDestroyed: false
46 sourceComponent: Component {
47 Greeter {
48 width: loader.width
49 height: loader.height
50 background: defaultBackground
51 viewSource: Qt.resolvedUrl("TestView.qml")
52
53 Component.onDestruction: {
54 loader.itemDestroyed = true;
55 }
56 }
57 }
58 }
59
60 SignalSpy {
61 id: teaseSpy
62 target: loader.item
63 signalName: "tease"
64 }
65
66 SignalSpy {
67 id: sessionStartedSpy
68 target: loader.item
69 signalName: "sessionStarted"
70 }
71
72 SignalSpy {
73 id: emergencyCallSpy
74 target: loader.item
75 signalName: "emergencyCall"
76 }
77
78 UT.UnityTestCase {
79 id: testCase
80 name: "Greeter"
81 when: windowShown
82
83 property Item greeter: loader.status === Loader.Ready ? loader.item : null
84 property Item view
85
86 SignalSpy {
87 id: viewShowMessageSpy
88 target: testCase.view
89 signalName: "_showMessageCalled"
90 }
91
92 SignalSpy {
93 id: viewShowPromptSpy
94 target: testCase.view
95 signalName: "_showPromptCalled"
96 }
97
98 SignalSpy {
99 id: viewShowLastChanceSpy
100 target: testCase.view
101 signalName: "_showLastChanceCalled"
102 }
103
104 SignalSpy {
105 id: viewHideSpy
106 target: testCase.view
107 signalName: "_hideCalled"
108 }
109
110 SignalSpy {
111 id: viewAuthenticationSucceededSpy
112 target: testCase.view
113 signalName: "_notifyAuthenticationSucceededCalled"
114 }
115
116 SignalSpy {
117 id: viewAuthenticationFailedSpy
118 target: testCase.view
119 signalName: "_notifyAuthenticationFailedCalled"
120 }
121
122 SignalSpy {
123 id: viewResetSpy
124 target: testCase.view
125 signalName: "_resetCalled"
126 }
127
128 SignalSpy {
129 id: viewTryToUnlockSpy
130 target: testCase.view
131 signalName: "_tryToUnlockCalled"
132 }
133
134 function init() {
135 teaseSpy.clear();
136 sessionStartedSpy.clear();
137 emergencyCallSpy.clear();
138 viewShowMessageSpy.clear();
139 viewShowPromptSpy.clear();
140 viewShowLastChanceSpy.clear();
141 viewHideSpy.clear();
142 viewAuthenticationSucceededSpy.clear();
143 viewAuthenticationFailedSpy.clear();
144 viewResetSpy.clear();
145 viewTryToUnlockSpy.clear();
146 tryCompare(greeter, "waiting", false);
147 view = findChild(greeter, "testView");
148 verifySelected(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
149 }
150
151 function cleanup() {
152 loader.itemDestroyed = false;
153 loader.active = false;
154 tryCompare(loader, "status", Loader.Null);
155 tryCompare(loader, "item", null);
156 tryCompare(loader, "itemDestroyed", true);
157 loader.active = true;
158 tryCompare(loader, "status", Loader.Ready);
159 removeTimeConstraintsFromDirectionalDragAreas(loader.item);
160 }
161
162 function getIndexOf(name) {
163 for (var i = 0; i < LightDM.Users.count; i++) {
164 if (name === LightDM.Users.data(i, LightDM.UserRoles.NameRole)) {
165 return i;
166 }
167 }
168 fail("Didn't find name")
169 return -1;
170 }
171
172 function selectUser(name) {
173 var i = getIndexOf(name);
174 view.selected(i);
175 verifySelected(name);
176 return i;
177 }
178
179 function verifySelected(name) {
180 var i = getIndexOf(name);
181 compare(view.currentIndex, i);
182 compare(AccountsService.user, name);
183 compare(LightDM.Greeter.authenticationUser, name);
184 }
185
186 function verifyLoggedIn() {
187 tryCompare(sessionStartedSpy, "count", 1);
188 verify(viewAuthenticationSucceededSpy.count > 0);
189 compare(LightDM.Greeter.authenticated, true);
190 compare(greeter.shown, false);
191 }
192
193 function test_unlockPass() {
194 selectUser("has-password");
195 tryCompare(viewShowPromptSpy, "count", 1);
196 compare(viewShowPromptSpy.signalArguments[0][0], "Password");
197 compare(viewShowPromptSpy.signalArguments[0][1], true);
198 compare(viewShowPromptSpy.signalArguments[0][2], true);
199
200 view.responded("password");
201 verifyLoggedIn();
202 }
203
204 function test_unlockFail() {
205 selectUser("has-password");
206 tryCompare(viewShowPromptSpy, "count", 1);
207 compare(viewShowPromptSpy.signalArguments[0][0], "Password");
208 compare(viewShowPromptSpy.signalArguments[0][1], true);
209 compare(viewShowPromptSpy.signalArguments[0][2], true);
210
211 view.responded("wr0ng p4ssw0rd");
212 tryCompare(viewAuthenticationFailedSpy, "count", 1);
213
214 tryCompare(viewShowPromptSpy, "count", 2);
215 compare(viewShowPromptSpy.signalArguments[0][0], "Password");
216 compare(viewShowPromptSpy.signalArguments[0][1], true);
217 compare(viewShowPromptSpy.signalArguments[0][2], true);
218 }
219
220 function test_promptless() {
221 selectUser("no-password");
222 tryCompare(viewAuthenticationSucceededSpy, "count", 1);
223 compare(sessionStartedSpy.count, 1);
224 compare(viewShowPromptSpy.count, 0);
225 compare(viewHideSpy.count, 0);
226 compare(view.locked, false);
227 }
228
229 function test_twoFactorPass() {
230 selectUser("two-factor");
231 tryCompare(viewShowPromptSpy, "count", 1);
232 compare(viewShowPromptSpy.signalArguments[0][0], "Password");
233 compare(viewShowPromptSpy.signalArguments[0][1], true);
234 compare(viewShowPromptSpy.signalArguments[0][2], true);
235
236 view.responded("password");
237 tryCompare(viewShowPromptSpy, "count", 2);
238 compare(viewShowPromptSpy.signalArguments[1][0], "otp");
239 compare(viewShowPromptSpy.signalArguments[1][1], false);
240 compare(viewShowPromptSpy.signalArguments[1][2], false);
241
242 view.responded("otp");
243 verifyLoggedIn();
244 }
245
246 function test_twoFactorFailOnFirst() {
247 selectUser("two-factor");
248 tryCompare(viewShowPromptSpy, "count", 1);
249 compare(viewShowPromptSpy.signalArguments[0][0], "Password");
250 compare(viewShowPromptSpy.signalArguments[0][1], true);
251 compare(viewShowPromptSpy.signalArguments[0][2], true);
252
253 view.responded("wr0ng p4ssw0rd");
254 tryCompare(viewAuthenticationFailedSpy, "count", 1);
255
256 tryCompare(viewShowPromptSpy, "count", 2);
257 compare(viewShowPromptSpy.signalArguments[0][0], "Password");
258 compare(viewShowPromptSpy.signalArguments[0][1], true);
259 compare(viewShowPromptSpy.signalArguments[0][2], true);
260 }
261
262 function test_twoFactorFailOnSecond() {
263 selectUser("two-factor");
264 tryCompare(viewShowPromptSpy, "count", 1);
265 compare(viewShowPromptSpy.signalArguments[0][0], "Password");
266 compare(viewShowPromptSpy.signalArguments[0][1], true);
267 compare(viewShowPromptSpy.signalArguments[0][2], true);
268
269 view.responded("password");
270 tryCompare(viewShowPromptSpy, "count", 2);
271 compare(viewShowPromptSpy.signalArguments[1][0], "otp");
272 compare(viewShowPromptSpy.signalArguments[1][1], false);
273 compare(viewShowPromptSpy.signalArguments[1][2], false);
274
275 view.responded("wr0ng p4ssw0rd");
276 tryCompare(viewAuthenticationFailedSpy, "count", 1);
277
278 tryCompare(viewShowPromptSpy, "count", 3);
279 compare(viewShowPromptSpy.signalArguments[0][0], "Password");
280 compare(viewShowPromptSpy.signalArguments[0][1], true);
281 compare(viewShowPromptSpy.signalArguments[0][2], true);
282 }
283
284 function test_htmlInfoPrompt() {
285 selectUser("html-info-prompt");
286 tryCompare(viewShowPromptSpy, "count", 1);
287 compare(viewShowMessageSpy.count, 1);
288 compare(viewShowMessageSpy.signalArguments[0][0], "&lt;b&gt;&amp;&lt;/b&gt;");
289 }
290
291 function test_multiInfoPrompt() {
292 selectUser("multi-info-prompt");
293 tryCompare(viewShowPromptSpy, "count", 1);
294 compare(viewShowMessageSpy.count, 3);
295 compare(viewShowMessageSpy.signalArguments[0][0], "Welcome to Unity Greeter");
296 compare(viewShowMessageSpy.signalArguments[1][0], "<font color=\"#df382c\">This is an error</font>");
297 compare(viewShowMessageSpy.signalArguments[2][0], "You should have seen three messages");
298 }
299
300 function test_waiting() {
301 // Make sure we unset 'waiting' on prompt
302 selectUser("has-password");
303 compare(greeter.waiting, true);
304 tryCompare(greeter, "waiting", false);
305
306 // Make sure we unset 'waiting' on authentication
307 selectUser("no-password");
308 compare(greeter.waiting, true);
309 tryCompare(greeter, "waiting", false);
310 }
311
312 function test_locked() {
313 selectUser("has-password");
314 compare(view.locked, true);
315
316 LightDM.Greeter.active = false;
317 compare(view.locked, false);
318 LightDM.Greeter.active = true;
319
320 greeter.forcedUnlock = true;
321 compare(view.locked, false);
322 greeter.forcedUnlock = false;
323
324 selectUser("no-password");
325 tryCompare(view, "locked", false);
326 selectUser("has-password");
327 }
328
329 function test_fullyShown() {
330 compare(greeter.fullyShown, true);
331 view.hide();
332 compare(greeter.fullyShown, false);
333 }
334
335 function test_alphanumeric() {
336 selectUser("has-password");
337 compare(view.alphanumeric, true);
338 selectUser("has-pin");
339 compare(view.alphanumeric, false);
340 }
341
342 function test_background() {
343 greeter.background = "testing";
344 compare(view.background, Qt.resolvedUrl("testing"));
345 }
346
347 function test_notifyAboutToFocusApp() {
348 greeter.notifyAboutToFocusApp("fake-app");
349 compare(viewTryToUnlockSpy.count, 1);
350 compare(viewTryToUnlockSpy.signalArguments[0][0], false);
351 }
352
353 function test_notifyShowingDashFromDrag() {
354 compare(greeter.notifyShowingDashFromDrag("fake-app"), true);
355 compare(viewTryToUnlockSpy.count, 1);
356 compare(viewTryToUnlockSpy.signalArguments[0][0], true);
357 }
358
359 function test_dragHandleLeftMargin() {
360 compare(view.dragHandleLeftMargin, 0);
361 greeter.dragHandleLeftMargin = 5;
362 compare(view.dragHandleLeftMargin, 5);
363 }
364
365 function test_launcherOffset() {
366 compare(view.launcherOffset, 0);
367 greeter.launcherOffset = 5;
368 tryCompare(view, "launcherOffset", 5);
369 }
370
371 function test_laucherOffsetAnimation() {
372 // Our logic for smoothing launcherOffset when it suddenly goes to
373 // zero is a bit complicated. Let's just make sure it works here.
374
375 launcherOffsetWatcher.target = view;
376
377 // should follow immediately
378 launcherOffsetWatcher.values = [];
379 greeter.launcherOffset = 100;
380 compare(view.launcherOffset, 100);
381 compare(launcherOffsetWatcher.values.length, 1);
382
383 // should interpolate values until it reaches 0
384 launcherOffsetWatcher.values = [];
385 greeter.launcherOffset = 0;
386 tryCompare(view, "launcherOffset", 0);
387 verify(launcherOffsetWatcher.values.length > 1);
388 for (var i = 0; i < launcherOffsetWatcher.values.length - 1; ++i) {
389 verify(launcherOffsetWatcher.values[i] > 0.0);
390 verify(launcherOffsetWatcher.values[i] < 100.0);
391 }
392 }
393 Connections {
394 id: launcherOffsetWatcher
395 property var values: []
396 onLauncherOffsetChanged: {
397 values.push(target.launcherOffset);
398 }
399 }
400
401 function test_backgroundTopMargin() {
402 compare(view.backgroundTopMargin, 0);
403 greeter.y = 5;
404 compare(view.backgroundTopMargin, -5);
405 }
406
407 function test_differentPrompt() {
408 selectUser("different-prompt");
409 tryCompare(viewShowPromptSpy, "count", 1);
410 compare(viewShowPromptSpy.signalArguments[0][0], "Secret word");
411 compare(viewShowPromptSpy.signalArguments[0][1], true);
412 compare(viewShowPromptSpy.signalArguments[0][2], false);
413 }
414
415 function test_authError() {
416 selectUser("auth-error");
417 tryCompare(viewAuthenticationFailedSpy, "count", 1);
418 compare(viewShowPromptSpy.count, 0);
419 compare(view.locked, true);
420 }
421
422 function test_statsWelcomeScreen() {
423 // Test logic in greeter that turns statsWelcomeScreen setting into infographic changes
424 selectUser("has-password");
425 compare(LightDM.Infographic.username, "has-password");
426 AccountsService.statsWelcomeScreen = false;
427 compare(LightDM.Infographic.username, "");
428 AccountsService.statsWelcomeScreen = true;
429 compare(LightDM.Infographic.username, "has-password");
430 }
431
432 function test_dbusRequestAuthenticationUser() {
433 selectUser("no-password");
434 LightDM.Greeter.requestAuthenticationUser("has-password");
435 verifySelected("has-password");
436 }
437
438 function test_dbusHideGreeter() {
439 compare(view.required, true);
440 LightDM.Greeter.hideGreeter();
441 compare(view.required, false);
442 compare(greeter.required, false);
443 }
444
445 function test_dbusShowGreeterFromHiddenState() {
446 greeter.hide();
447 compare(greeter.required, false);
448
449 LightDM.Greeter.showGreeter();
450 compare(greeter.required, true);
451 compare(greeter.fullyShown, true);
452 view = findChild(greeter, "testView");
453 compare(view.required, true);
454
455 // Can't test some of the stuff called on 'view' here because
456 // the view was torn down and created again. So the spies missed
457 // the good stuff while it down. See next test for more.
458 }
459
460 function test_dbusShowGreeterFromShownState() {
461 selectUser("has-password");
462 compare(viewResetSpy.count, 1);
463 tryCompare(viewShowPromptSpy, "count", 1);
464
465 viewResetSpy.clear();
466 viewShowPromptSpy.clear();
467
468 LightDM.Greeter.showGreeter();
469 compare(viewResetSpy.count, 1);
470 }
471 }
472}
0473
=== removed file 'tests/qmltests/Greeter/tst_MultiGreeter.qml'
--- tests/qmltests/Greeter/tst_MultiGreeter.qml 2015-02-02 14:28:32 +0000
+++ tests/qmltests/Greeter/tst_MultiGreeter.qml 1970-01-01 00:00:00 +0000
@@ -1,475 +0,0 @@
1/*
2 * Copyright 2013 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.0
18import QtQuick.Layouts 1.1
19import QtTest 1.0
20import ".."
21import "../../../qml/Greeter"
22import Ubuntu.Components 0.1
23import LightDM 0.1 as LightDM
24import Unity.Test 0.1 as UT
25
26Item {
27 id: root
28 width: units.gu(140)
29 height: units.gu(80)
30
31 QtObject {
32 id: fakeInputMethod
33 property bool visible: fakeKeyboard.visible
34 property var keyboardRectangle: QtObject {
35 property real x: fakeKeyboard.x
36 property real y: fakeKeyboard.y
37 property real width: fakeKeyboard.width
38 property real height: fakeKeyboard.height
39 }
40 }
41
42 Binding {
43 target: LightDM.Greeter
44 property: "mockMode"
45 value: "full"
46 }
47 Binding {
48 target: LightDM.Users
49 property: "mockMode"
50 value: "full"
51 }
52
53 Greeter {
54 id: greeter
55 width: units.gu(120)
56 height: root.height
57 inputMethod: fakeInputMethod
58 locked: !LightDM.Greeter.authenticated
59 }
60
61 Component {
62 id: greeterComponent
63 Greeter {
64 SignalSpy {
65 objectName: "selectedSpy"
66 target: parent
67 signalName: "selected"
68 }
69 }
70 }
71
72 Rectangle {
73 id: fakeKeyboard
74 color: "green"
75 opacity: 0.7
76 anchors.bottom: root.bottom
77 width: greeter.width
78 height: greeter.height * 0.6
79 visible: keyboardVisibleCheckbox.checked
80 Text {
81 text: "Keyboard Rectangle"
82 color: "yellow"
83 font.bold: true
84 fontSizeMode: Text.Fit
85 minimumPixelSize: 10; font.pixelSize: 200
86 verticalAlignment: Text.AlignVCenter
87 x: (parent.width - width) / 2
88 y: (parent.height - height) / 2
89 width: parent.width
90 height: parent.height
91 }
92 }
93
94 Item {
95 anchors {
96 top: root.top
97 bottom: root.bottom
98 left: greeter.right
99 right: root.right
100 }
101 RowLayout {
102 Layout.fillWidth: true
103 CheckBox {
104 id: keyboardVisibleCheckbox
105 }
106 Label { text: "Keyboard Visible"; anchors.verticalCenter: parent.verticalCenter }
107 }
108 }
109
110 SignalSpy {
111 id: unlockSpy
112 target: greeter
113 signalName: "unlocked"
114 }
115
116 SignalSpy {
117 id: selectionSpy
118 target: greeter
119 signalName: "selected"
120 }
121
122 SignalSpy {
123 id: tappedSpy
124 target: greeter
125 signalName: "tapped"
126 }
127
128 UT.UnityTestCase {
129 name: "MultiGreeter"
130 when: windowShown
131
132 function cleanup() {
133 keyboardVisibleCheckbox.checked = false;
134 }
135
136 function select_index(i) {
137 // We could be anywhere in list; find target index to know which direction
138 var userlist = findChild(greeter, "userList")
139 if (userlist.currentIndex == i)
140 keyClick(Qt.Key_Escape) // Reset state if we're not moving
141 while (userlist.currentIndex != i) {
142 var next = userlist.currentIndex + 1
143 if (userlist.currentIndex > i) {
144 next = userlist.currentIndex - 1
145 }
146 var account = findChild(greeter, "username"+next)
147 mouseClick(account, 1, 1)
148 tryCompare(userlist, "currentIndex", next)
149 tryCompare(userlist, "movingInternally", false)
150 }
151 }
152
153 function select_user(name) {
154 // We could be anywhere in list; find target index to know which direction
155 for (var i = 0; i < greeter.model.count; i++) {
156 if (greeter.model.data(i, LightDM.UserRoles.NameRole) == name) {
157 break
158 }
159 }
160 if (i == greeter.model.count) {
161 fail("Didn't find name")
162 return -1
163 }
164 select_index(i)
165 return i
166 }
167
168 function test_properties() {
169 compare(greeter.multiUser, true)
170 compare(greeter.narrowMode, false)
171 }
172
173 function test_cycle_data() {
174 var data = new Array()
175 for (var i = 0; i < greeter.model.count; i++) {
176 data[i] = {tag: greeter.model.data(i, LightDM.UserRoles.NameRole), uid: i }
177 }
178 return data
179 }
180
181 function test_cycle(data) {
182 selectionSpy.clear();
183 var userList = findChild(greeter, "userList")
184 var waitForSignal = data.uid != 0 && userList.currentIndex != data.uid
185 select_index(data.uid)
186 tryCompare(userList, "currentIndex", data.uid)
187 tryCompare(greeter, "locked", data.tag !== "no-password")
188 if (waitForSignal) {
189 selectionSpy.wait()
190 tryCompare(selectionSpy, "count", 1)
191 }
192 }
193
194 function test_unlock_password() {
195 select_user("no-password") // to guarantee a selected signal
196 unlockSpy.clear()
197 select_user("has-password")
198 var passwordInput = findChild(greeter, "passwordInput")
199 tryCompare(passwordInput, "opacity", 1)
200 mouseClick(passwordInput, 1, 1)
201 compare(unlockSpy.count, 0)
202 typeString("password")
203 keyClick(Qt.Key_Enter)
204 unlockSpy.wait()
205 }
206
207 function test_unlock_wrong_password() {
208 select_user("no-password") // to guarantee a selected signal
209 unlockSpy.clear()
210 select_user("has-password")
211 wait(0) // spin event loop to start any pending animations
212 var passwordInput = findChild(greeter, "passwordInput")
213 tryCompare(passwordInput, "opacity", 1) // wait for opacity animation to be finished
214 mouseClick(passwordInput, 1, 1)
215 compare(unlockSpy.count, 0)
216 typeString("wr0ng p4ssw0rd")
217 keyClick(Qt.Key_Enter)
218 compare(unlockSpy.count, 0)
219 }
220
221 function test_unlock_no_password() {
222 unlockSpy.clear()
223 select_user("no-password")
224 var passwordInput = findChild(greeter, "passwordInput")
225 tryCompare(passwordInput, "opacity", 1)
226 mouseClick(passwordInput, 1, 1)
227 unlockSpy.wait()
228 compare(unlockSpy.count, 1)
229 }
230
231 function test_empty_name() {
232 for (var i = 0; i < greeter.model.count; i++) {
233 if (greeter.model.data(i, LightDM.UserRoles.NameRole) == "empty-name") {
234 compare(greeter.model.data(i, LightDM.UserRoles.RealNameRole), greeter.model.data(i, LightDM.UserRoles.NameRole))
235 return
236 }
237 }
238 fail("Didn't find empty-name")
239 }
240
241 function test_auth_error() {
242 select_user("auth-error")
243 var passwordInput = findChild(greeter, "passwordInput")
244 tryCompare(passwordInput, "placeholderText", "Retry")
245 }
246
247 function test_different_prompt() {
248 select_user("different-prompt")
249 var passwordInput = findChild(greeter, "passwordInput")
250 tryCompare(passwordInput, "placeholderText", "Secret word")
251 }
252
253 function test_no_response() {
254 unlockSpy.clear()
255 select_user("no-response")
256 var passwordInput = findChild(greeter, "passwordInput")
257 tryCompare(passwordInput, "opacity", 1)
258 mouseClick(passwordInput, 1, 1)
259 compare(unlockSpy.count, 0)
260 typeString("password")
261 keyClick(Qt.Key_Enter)
262 tryCompare(passwordInput, "enabled", false)
263 keyClick(Qt.Key_Escape)
264 tryCompare(passwordInput, "enabled", true)
265 compare(unlockSpy.count, 0)
266 }
267
268 function test_two_factor_correct() {
269 unlockSpy.clear()
270 select_user("two-factor")
271 var passwordInput = findChild(greeter, "passwordInput")
272 tryCompare(passwordInput, "opacity", 1)
273 tryCompare(passwordInput, "echoMode", TextInput.Password)
274 tryCompare(passwordInput, "placeholderText", "Password")
275 mouseClick(passwordInput, 1, 1)
276 compare(unlockSpy.count, 0)
277 typeString("password")
278 keyClick(Qt.Key_Enter)
279 tryCompare(passwordInput, "echoMode", TextInput.Normal)
280 tryCompare(passwordInput, "placeholderText", "otp")
281 tryCompare(passwordInput, "enabled", true)
282 typeString("otp")
283 keyClick(Qt.Key_Enter)
284 unlockSpy.wait()
285 }
286
287 function test_two_factor_wrong1() {
288 unlockSpy.clear()
289 select_user("two-factor")
290 var passwordInput = findChild(greeter, "passwordInput")
291 tryCompare(passwordInput, "opacity", 1)
292 tryCompare(passwordInput, "placeholderText", "Password")
293 mouseClick(passwordInput, 1, 1)
294 compare(unlockSpy.count, 0)
295 typeString("wr0ng p4ssw0rd")
296 keyClick(Qt.Key_Enter)
297 tryCompare(passwordInput, "placeholderText", "Password")
298 tryCompare(passwordInput, "enabled", true)
299 compare(unlockSpy.count, 0)
300 }
301
302 function test_two_factor_wrong2() {
303 unlockSpy.clear()
304 select_user("two-factor")
305 var passwordInput = findChild(greeter, "passwordInput")
306 tryCompare(passwordInput, "opacity", 1)
307 tryCompare(passwordInput, "placeholderText", "Password")
308 mouseClick(passwordInput, 1, 1)
309 compare(unlockSpy.count, 0)
310 typeString("password")
311 keyClick(Qt.Key_Enter)
312 tryCompare(passwordInput, "placeholderText", "otp")
313 tryCompare(passwordInput, "enabled", true)
314 typeString("wr0ng p4ssw0rd")
315 keyClick(Qt.Key_Enter)
316 tryCompare(passwordInput, "placeholderText", "Password")
317 tryCompare(passwordInput, "enabled", true)
318 compare(unlockSpy.count, 0)
319 }
320
321 function test_unicode() {
322 var index = select_user("unicode")
323 var label = findChild(greeter, "username"+index)
324 tryCompare(label, "text", "가나다라마")
325 }
326
327 function test_long_name() {
328 var index = select_user("long-name")
329 var label = findChild(greeter, "username"+index)
330 tryCompare(label, "truncated", true)
331 }
332
333 function test_info_prompt() {
334 select_user("info-prompt")
335 var label = findChild(greeter, "infoLabel")
336 tryCompare(label, "text", "Welcome to Unity Greeter")
337 tryCompare(label, "opacity", 1)
338 tryCompare(label, "clip", true)
339 tryCompareFunction(function() {return label.contentWidth > label.width;}, false) // c.f. wide-info-prompt
340 var passwordInput = findChild(greeter, "passwordInput")
341 mouseClick(passwordInput, 1, 1)
342 keyClick(Qt.Key_Escape)
343 }
344
345 function test_info_prompt_escape() {
346 select_user("info-prompt")
347 var passwordInput = findChild(greeter, "passwordInput")
348 mouseClick(passwordInput, 1, 1)
349 keyClick(Qt.Key_Escape)
350 var label = findChild(greeter, "infoLabel")
351 tryCompare(label, "text", "Welcome to Unity Greeter")
352 tryCompare(label, "opacity", 1)
353 }
354
355 function test_wide_info_prompt() {
356 select_user("wide-info-prompt")
357 var label = findChild(greeter, "infoLabel")
358 tryCompare(label, "clip", true)
359 tryCompareFunction(function() {return label.contentWidth > label.width;}, true)
360 }
361
362 function test_html_info_prompt() {
363 select_user("html-info-prompt")
364 var label = findChild(greeter, "infoLabel")
365 tryCompare(label, "text", "&lt;b&gt;&amp;&lt;/b&gt;")
366 }
367
368 function test_long_info_prompt() {
369 select_user("long-info-prompt")
370 var label = findChild(greeter, "infoLabel")
371 tryCompare(label, "text", "Welcome to Unity Greeter<br><br>We like to annoy you with super ridiculously long messages.<br>Like this one<br><br>This is the last line of a multiple line message.")
372 tryCompare(label, "textFormat", Text.StyledText) // for parsing above correctly
373 tryCompare(label, "clip", true)
374 tryCompareFunction(function() {return label.contentWidth > label.width;}, true)
375 }
376
377 function test_multi_info_prompt() {
378 select_user("multi-info-prompt")
379 var label = findChild(greeter, "infoLabel")
380 tryCompare(label, "text", "Welcome to Unity Greeter<br><font color=\"#df382c\">This is an error</font><br>You should have seen three messages")
381 tryCompare(label, "textFormat", Text.StyledText) // for parsing above correctly
382 }
383
384 function test_bg_color() {
385 var index = select_user("color-background")
386 compare(greeter.model.data(index, LightDM.UserRoles.BackgroundPathRole), "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>")
387 }
388
389 function test_bg_none() {
390 var index = select_user("no-background")
391 compare(greeter.model.data(index, LightDM.UserRoles.BackgroundPathRole), "")
392 }
393
394 function test_tappedSignal_data() {
395 return [
396 {tag: "left", posX: units.gu(2)},
397 {tag: "right", posX: greeter.width - units.gu(2)}
398 ]
399 }
400
401 function test_tappedSignal(data) {
402 select_user("no-password");
403 tappedSpy.clear();
404 tap(greeter, data.posX, greeter.height - units.gu(1))
405 tryCompare(tappedSpy, "count", 1)
406 }
407
408 function test_teaseLockedUnlocked_data() {
409 return [
410 {tag: "unlocked", locked: false, narrow: false},
411 {tag: "locked", locked: true, narrow: false},
412 ];
413 }
414
415 function test_teaseLockedUnlocked(data) {
416 tappedSpy.clear()
417 greeter.locked = data.locked;
418
419 tap(greeter, greeter.width - units.gu(5), greeter.height - units.gu(1));
420
421 if (!data.locked || data.narrow) {
422 tappedSpy.wait()
423 tryCompare(tappedSpy, "count", 1);
424 } else {
425 // waiting 100ms to make sure nothing happens
426 wait(100);
427 compare(tappedSpy.count, 0, "Greeter teasing not disabled even though it's locked.");
428 }
429
430 // Reset value
431 greeter.locked = false;
432 }
433
434 function test_dbus_set_active_entry() {
435 select_user("no-password") // to guarantee a selected signal
436 selectionSpy.clear()
437 LightDM.Greeter.requestAuthenticationUser("has-password")
438
439 selectionSpy.wait()
440 tryCompare(selectionSpy, "count", 1)
441
442 var userlist = findChild(greeter, "userList")
443 compare(greeter.model.data(userlist.currentIndex, LightDM.UserRoles.NameRole), "has-password")
444 }
445
446 function test_initial_selected_signal() {
447 var greeterObj = greeterComponent.createObject(this)
448 var spy = findChild(greeterObj, "selectedSpy")
449 spy.wait()
450 tryCompare(spy, "count", 1)
451 greeterObj.destroy()
452 }
453
454 function test_login_list_not_covered_by_keyboard() {
455 var loginList = findChild(greeter, "loginLoader").item;
456 compare(loginList.height, greeter.height);
457
458 // when the vkb shows up, loginList is moved up to remain fully uncovered
459
460 keyboardVisibleCheckbox.checked = true;
461
462 tryCompare(loginList, "height", greeter.height - fakeInputMethod.keyboardRectangle.height);
463 tryCompareFunction( function() {
464 var loginListRect = loginList.mapToItem(greeter, 0, 0, loginList.width, loginList.height);
465 return loginListRect.y + loginListRect.height <= fakeInputMethod.keyboardRectangle.y;
466 }, true);
467
468 // once the vkb goes away, loginList goes back to its full height
469
470 keyboardVisibleCheckbox.checked = false;
471
472 tryCompare(loginList, "height", greeter.height);
473 }
474 }
475}
4760
=== added file 'tests/qmltests/Greeter/tst_NarrowView.qml'
--- tests/qmltests/Greeter/tst_NarrowView.qml 1970-01-01 00:00:00 +0000
+++ tests/qmltests/Greeter/tst_NarrowView.qml 2015-02-23 15:44:00 +0000
@@ -0,0 +1,522 @@
1/*
2 * Copyright 2014 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.0
18import QtTest 1.0
19import ".."
20import "../../../qml/Greeter"
21import LightDM 0.1 as LightDM
22import Ubuntu.Components 0.1
23import Unity.Test 0.1 as UT
24
25Item {
26 id: root
27 width: units.gu(90)
28 height: units.gu(80)
29
30 Row {
31 anchors.fill: parent
32 Loader {
33 id: loader
34 width: root.width - controls.width
35 height: parent.height
36
37 property bool itemDestroyed: false
38 sourceComponent: Component {
39 NarrowView {
40 background: Qt.resolvedUrl("../../../qml/graphics/phone_background.jpg")
41 userModel: LightDM.Users
42 infographicModel: LightDM.Infographic
43
44 launcherOffset: parseFloat(launcherOffsetField.text)
45 currentIndex: parseInt(currentIndexField.text, 10)
46 delayMinutes: parseInt(delayMinutesField.text, 10)
47 backgroundTopMargin: parseFloat(backgroundTopMarginField.text)
48 locked: lockedCheckBox.checked
49 alphanumeric: alphanumericCheckBox.checked
50
51 Component.onDestruction: {
52 loader.itemDestroyed = true
53 }
54
55 onSelected: {
56 currentIndexField.text = index;
57 }
58 }
59 }
60 }
61
62 Rectangle {
63 id: controls
64 color: "white"
65 width: units.gu(40)
66 height: parent.height
67
68 Column {
69 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
70 spacing: units.gu(1)
71
72 Row {
73 Button {
74 text: "Show Last Chance"
75 onClicked: loader.item.showLastChance()
76 }
77 }
78 Row {
79 Button {
80 text: "Hide"
81 onClicked: loader.item.hide()
82 }
83 }
84 Row {
85 Button {
86 text: "Reset"
87 onClicked: loader.item.reset()
88 }
89 }
90 Row {
91 Button {
92 text: "Show Message"
93 onClicked: loader.item.showMessage(messageField.text)
94 }
95 TextField {
96 id: messageField
97 width: units.gu(10)
98 text: ""
99 }
100 }
101 Row {
102 Button {
103 text: "Show Prompt"
104 onClicked: loader.item.showPrompt(promptField.text, isSecretCheckBox.checked, isDefaultPromptCheckBox.checked)
105 }
106 TextField {
107 id: promptField
108 width: units.gu(10)
109 text: ""
110 }
111 CheckBox {
112 id: isSecretCheckBox
113 }
114 Label {
115 text: "secret"
116 }
117 CheckBox {
118 id: isDefaultPromptCheckBox
119 }
120 Label {
121 text: "default"
122 }
123 }
124 Row {
125 Button {
126 text: "Authenticated"
127 onClicked: {
128 if (successCheckBox.checked) {
129 loader.item.notifyAuthenticationSucceeded();
130 } else {
131 loader.item.notifyAuthenticationFailed();
132 }
133 }
134 }
135 CheckBox {
136 id: successCheckBox
137 }
138 Label {
139 text: "success"
140 }
141 }
142 Row {
143 Button {
144 text: "Try To Unlock"
145 onClicked: loader.item.tryToUnlock(toTheRightCheckBox.checked)
146 }
147 CheckBox {
148 id: toTheRightCheckBox
149 }
150 Label {
151 text: "toTheRight"
152 }
153 }
154 Row {
155 TextField {
156 id: launcherOffsetField
157 width: units.gu(10)
158 text: "0"
159 }
160 Label {
161 text: "launcherOffset"
162 }
163 }
164 Row {
165 TextField {
166 id: currentIndexField
167 width: units.gu(10)
168 text: "0"
169 }
170 Label {
171 text: "currentIndex"
172 }
173 }
174 Row {
175 TextField {
176 id: delayMinutesField
177 width: units.gu(10)
178 text: "0"
179 }
180 Label {
181 text: "delayMinutes"
182 }
183 }
184 Row {
185 TextField {
186 id: backgroundTopMarginField
187 width: units.gu(10)
188 text: "0"
189 }
190 Label {
191 text: "backgroundTopMargin"
192 }
193 }
194 Row {
195 CheckBox {
196 id: lockedCheckBox
197 }
198 Label {
199 text: "locked"
200 }
201 }
202 Row {
203 CheckBox {
204 id: alphanumericCheckBox
205 }
206 Label {
207 text: "alphanumeric"
208 }
209 }
210 Row {
211 Label {
212 text: "selected: " + selectedSpy.count
213 }
214 }
215 Row {
216 Label {
217 text: "responded: " + respondedSpy.count
218 }
219 }
220 Row {
221 Label {
222 text: "teased: " + teaseSpy.count
223 }
224 }
225 Row {
226 Label {
227 text: "emergency: " + emergencySpy.count
228 }
229 }
230 Row {
231 Button {
232 text: "Reload View"
233 onClicked: {
234 loader.active = false;
235 loader.active = true;
236 }
237 }
238 }
239 }
240 }
241 }
242
243 Binding {
244 target: LightDM.Infographic
245 property: "username"
246 value: "single"
247 }
248
249 SignalSpy {
250 id: selectedSpy
251 target: loader.item
252 signalName: "selected"
253 }
254
255 SignalSpy {
256 id: respondedSpy
257 target: loader.item
258 signalName: "responded"
259 }
260
261 SignalSpy {
262 id: teaseSpy
263 target: loader.item
264 signalName: "tease"
265 }
266
267 SignalSpy {
268 id: emergencySpy
269 target: loader.item
270 signalName: "emergencyCall"
271 }
272
273 SignalSpy {
274 id: infographicDataChangedSpy
275 target: LightDM.Infographic
276 signalName: "dataChanged"
277 }
278
279 UT.UnityTestCase {
280 name: "NarrowView"
281 when: windowShown
282
283 property Item view: loader.status === Loader.Ready ? loader.item : null
284
285 function init() {
286 view.currentIndex = 0; // break binding with text field
287
288 selectedSpy.clear();
289 respondedSpy.clear();
290 teaseSpy.clear();
291 emergencySpy.clear();
292 infographicDataChangedSpy.clear();
293 }
294
295 function cleanup() {
296 loader.itemDestroyed = false;
297 loader.active = false;
298 tryCompare(loader, "status", Loader.Null);
299 tryCompare(loader, "item", null);
300 tryCompare(loader, "itemDestroyed", true);
301 loader.active = true;
302 tryCompare(loader, "status", Loader.Ready);
303 removeTimeConstraintsFromDirectionalDragAreas(loader.item);
304 }
305
306 function swipeAwayCover(toTheRight) {
307 if (toTheRight === undefined) {
308 toTheRight = false;
309 }
310
311 tryCompare(view, "fullyShown", true);
312 var touchY = view.height / 2;
313 if (toTheRight) {
314 touchFlick(view, 0, touchY, view.width, touchY);
315 } else {
316 touchFlick(view, view.width, touchY, 0, touchY);
317 }
318 var coverPage = findChild(view, "coverPage");
319 tryCompare(coverPage, "showProgress", 0);
320 waitForRendering(view);
321 }
322
323 function enterPin(pin) {
324 for (var i = 0; i < pin.length; ++i) {
325 var character = pin.charAt(i);
326 var button = findChild(view, "pinPadButton" + character);
327 tap(button);
328 }
329 }
330
331 function test_tease_data() {
332 return [
333 {tag: "left", x: 0, offset: 0, count: 1},
334 {tag: "leftWithOffsetPass", x: 10, offset: 10, count: 1},
335 {tag: "leftWithOffsetFail", x: 9, offset: 10, count: 0},
336 {tag: "right", x: view.width, offset: 0, count: 1},
337 ]
338 }
339 function test_tease(data) {
340 view.dragHandleLeftMargin = data.offset;
341 tap(view, data.x, 0);
342 compare(teaseSpy.count, data.count);
343 }
344
345 function test_respondedWithPin() {
346 view.locked = true;
347 swipeAwayCover();
348 enterPin("1234");
349 compare(respondedSpy.count, 1);
350 compare(respondedSpy.signalArguments[0][0], "1234");
351 }
352
353 function test_respondedWithPassphrase() {
354 view.locked = true;
355 view.alphanumeric = true;
356 swipeAwayCover();
357 typeString("test");
358 keyClick(Qt.Key_Enter);
359 compare(respondedSpy.count, 1);
360 compare(respondedSpy.signalArguments[0][0], "test");
361 }
362
363 function test_respondedWithSwipe_data() {
364 return [
365 {tag: "left", toTheRight: false, hiddenX: -view.width},
366 {tag: "right", toTheRight: true, hiddenX: view.width},
367 ];
368 }
369 function test_respondedWithSwipe(data) {
370 swipeAwayCover(data.toTheRight);
371 var coverPage = findChild(view, "coverPage");
372 compare(coverPage.x, data.hiddenX);
373 compare(respondedSpy.count, 1);
374 compare(respondedSpy.signalArguments[0][0], "");
375 }
376
377 function test_emergencyCall() {
378 view.locked = true;
379 swipeAwayCover();
380 var emergencyCallLabel = findChild(view, "emergencyCallLabel");
381 tap(emergencyCallLabel);
382 compare(emergencySpy.count, 1);
383 }
384
385 function test_fullyShown() {
386 tryCompare(view, "fullyShown", true);
387 swipeAwayCover();
388 tryCompare(view, "fullyShown", false);
389 view.locked = true;
390 tryCompare(view, "fullyShown", true);
391 view.locked = false;
392 tryCompare(view, "fullyShown", false);
393 }
394
395 function test_required() {
396 tryCompare(view, "required", true);
397 swipeAwayCover();
398 tryCompare(view, "required", false);
399 view.locked = true;
400 tryCompare(view, "required", true);
401 view.locked = false;
402 tryCompare(view, "required", false);
403 }
404
405 function test_tryToUnlock() {
406 var coverPage = findChild(view, "coverPage");
407 tryCompare(coverPage, "showProgress", 1);
408 compare(view.tryToUnlock(false), true);
409 tryCompare(coverPage, "showProgress", 0);
410 compare(view.tryToUnlock(false), false);
411 }
412
413 /*
414 Regression test for https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1388359
415 "User metrics can no longer be changed by double tap"
416 */
417 function test_doubleTapSwitchesToNextInfographic() {
418 var infographicPrivate = findInvisibleChild(view, "infographicPrivate");
419 verify(infographicPrivate);
420
421 // wait for the UI to settle down before double tapping it
422 tryCompare(infographicPrivate, "animating", false);
423
424 var dataCircle = findChild(view, "dataCircle");
425 verify(dataCircle);
426
427 tap(dataCircle);
428 wait(1);
429 tap(dataCircle);
430
431 tryCompare(infographicDataChangedSpy, "count", 1);
432 }
433
434 function test_movesBackIntoPlaceWhenNotDraggedFarEnough() {
435 var coverPage = findChild(view, "coverPage");
436
437 var dragEvaluator = findInvisibleChild(coverPage, "edgeDragEvaluator");
438 verify(dragEvaluator);
439
440 // Make it easier to get a rejection/rollback. Otherwise would have to inject
441 // a fake timer into dragEvaluator.
442 // Afterall, we are testing if the CoverPage indeed moves back on a
443 // rollback decision, not the drag evaluation itself.
444 dragEvaluator.minDragDistance = dragEvaluator.maxDragDistance / 2;
445
446 // it starts as fully shown
447 compare(coverPage.x, 0);
448
449 // then we drag it a bit
450 var startX = coverPage.width - 1;
451 var touchY = coverPage.height / 2;
452 var dragXDelta = -(dragEvaluator.minDragDistance * 0.3);
453 touchFlick(coverPage,
454 startX , touchY, // start pos
455 startX + dragXDelta, touchY, // end pos
456 true /* beginTouch */, false /* endTouch */);
457
458 // which should make it move a bit
459 tryCompareFunction(function() {return coverPage.x < 0;}, true);
460
461 // then we release it
462 touchRelease(coverPage, startX + dragXDelta, touchY);
463
464 // which should make it move back into its original position as it didn't move
465 // far enough to have it hidden
466 tryCompare(coverPage, "x", 0);
467 }
468
469 function test_dragToHide_data() {
470 return [
471 {tag: "left", startX: view.width * 0.95, endX: view.width * 0.1, hiddenX: -view.width},
472 {tag: "right", startX: view.width * 0.1, endX: view.width * 0.95, hiddenX: view.width},
473 ];
474 }
475 function test_dragToHide(data) {
476 var coverPage = findChild(view, "coverPage");
477 compare(coverPage.x, 0);
478 compare(coverPage.visible, true);
479 compare(coverPage.shown, true);
480 compare(coverPage.showProgress, 1);
481 compare(view.fullyShown, true);
482
483 touchFlick(view,
484 data.startX, view.height / 2, // start pos
485 data.endX, view.height / 2); // end pos
486
487 tryCompare(coverPage, "x", data.hiddenX);
488 tryCompare(coverPage, "visible", false);
489 tryCompare(coverPage, "shown", false);
490 tryCompare(coverPage, "showProgress", 0);
491 compare(view.fullyShown, false);
492 }
493
494 function test_hiddenViewRemainsHiddenAfterResize_data() {
495 return [
496 {tag: "left", startX: view.width * 0.95, endX: view.width * 0.1},
497 {tag: "right", startX: view.width * 0.1, endX: view.width * 0.95},
498 ];
499 }
500 function test_hiddenViewRemainsHiddenAfterResize(data) {
501 touchFlick(view,
502 data.startX, view.height / 2, // start pos
503 data.endX, view.height / 2); // end pos
504
505 var coverPage = findChild(view, "coverPage");
506 tryCompare(coverPage, "x", data.tag == "left" ? -view.width : view.width);
507 tryCompare(coverPage, "visible", false);
508 tryCompare(coverPage, "shown", false);
509 tryCompare(coverPage, "showProgress", 0);
510
511 // flip dimensions to simulate an orientation change
512 view.width = loader.height;
513 view.height = loader.width;
514
515 // All properties should remain consistent
516 tryCompare(coverPage, "x", data.tag == "left" ? -view.width : view.width);
517 tryCompare(coverPage, "visible", false);
518 tryCompare(coverPage, "shown", false);
519 tryCompare(coverPage, "showProgress", 0);
520 }
521 }
522}
0523
=== removed file 'tests/qmltests/Greeter/tst_SingleGreeter.qml'
--- tests/qmltests/Greeter/tst_SingleGreeter.qml 2014-12-08 18:08:38 +0000
+++ tests/qmltests/Greeter/tst_SingleGreeter.qml 1970-01-01 00:00:00 +0000
@@ -1,244 +0,0 @@
1/*
2 * Copyright 2013 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.0
18import QtTest 1.0
19import ".."
20import "../../../qml/Greeter"
21import AccountsService 0.1
22import LightDM 0.1 as LightDM
23import Ubuntu.Components 0.1
24import Unity.Test 0.1 as UT
25
26Rectangle {
27 color: "darkblue"
28 width: units.gu(60)
29 height: units.gu(80)
30
31 Button {
32 anchors.centerIn: parent
33 text: "Show Greeter"
34 onClicked: {
35 if (greeterLoader.item)
36 greeterLoader.item.show();
37 }
38 }
39
40 Loader {
41 id: greeterLoader
42 anchors.fill: parent
43
44 property bool itemDestroyed: false
45
46 sourceComponent: Component {
47 Greeter {
48 width: greeterLoader.width
49 height: greeterLoader.height
50
51 Component.onDestruction: {
52 greeterLoader.itemDestroyed = true;
53 }
54 SignalSpy {
55 objectName: "selectedSpy"
56 target: parent
57 signalName: "selected"
58 }
59 }
60 }
61 }
62
63 SignalSpy {
64 id: unlockSpy
65 target: greeterLoader.item
66 signalName: "unlocked"
67 }
68
69 SignalSpy {
70 id: tappedSpy
71 target: greeterLoader.item
72 signalName: "tapped"
73 }
74
75 SignalSpy {
76 id: infographicDataChangedSpy
77 target: LightDM.Infographic
78 signalName: "dataChanged"
79 }
80
81 UT.UnityTestCase {
82 name: "SingleGreeter"
83 when: windowShown
84
85 property Greeter greeter: greeterLoader.item
86
87 function cleanup() {
88 AccountsService.statsWelcomeScreen = true
89
90 // force a reload so that we get a fresh Greeter for the next test
91 greeterLoader.itemDestroyed = false;
92 greeterLoader.active = false;
93 tryCompare(greeterLoader, "itemDestroyed", true);
94
95 unlockSpy.clear();
96 tappedSpy.clear();
97
98 greeterLoader.active = true;
99 tryCompare(greeterLoader, "status", Loader.Ready);
100 removeTimeConstraintsFromDirectionalDragAreas(greeterLoader.item);
101 }
102
103 function test_properties() {
104 compare(greeter.multiUser, false)
105 compare(greeter.narrowMode, true)
106 }
107
108 function test_teasingArea_data() {
109 return [
110 {tag: "left", posX: units.gu(2), leftPressed: true, rightPressed: false},
111 {tag: "right", posX: greeter.width - units.gu(2), leftPressed: false, rightPressed: true}
112 ]
113 }
114
115 function test_teasingArea(data) {
116 tappedSpy.clear()
117 tap(greeter, data.posX, greeter.height - units.gu(1))
118 tappedSpy.wait()
119 tryCompare(tappedSpy, "count", 1)
120 }
121
122 function test_statsWelcomeScreen() {
123 // Test logic in greeter that turns statsWelcomeScreen setting into infographic changes
124 compare(AccountsService.statsWelcomeScreen, true)
125 tryCompare(LightDM.Infographic, "username", "single")
126 AccountsService.statsWelcomeScreen = false
127 tryCompare(LightDM.Infographic, "username", "")
128 AccountsService.statsWelcomeScreen = true
129 tryCompare(LightDM.Infographic, "username", "single")
130 }
131
132 function test_initial_selected_signal() {
133 var selectedSpy = findChild(greeter, "selectedSpy");
134 selectedSpy.wait();
135 tryCompare(selectedSpy, "count", 1);
136 }
137
138 /*
139 Regression test for https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1388359
140 "User metrics can no longer be changed by double tap"
141 */
142 function test_doubleTapSwitchesToNextInfographic() {
143 infographicDataChangedSpy.clear();
144
145 var infographicPrivate = findInvisibleChild(greeter, "infographicPrivate");
146 verify(infographicPrivate);
147
148 // wait for the UI to settle down before double tapping it
149 tryCompare(infographicPrivate, "animating", false);
150
151 var dataCircle = findChild(greeter, "dataCircle");
152 verify(dataCircle);
153
154 tap(dataCircle);
155 wait(1);
156 tap(dataCircle);
157
158 tryCompare(infographicDataChangedSpy, "count", 1);
159 }
160
161 function test_movesBackIntoPlaceWhenNotDraggedFarEnough() {
162
163 var dragEvaluator = findInvisibleChild(greeter, "edgeDragEvaluator");
164 verify(dragEvaluator);
165
166 // Make it easier to get a rejection/rollback. Otherwise would have to inject
167 // a fake timer into dragEvaluator.
168 // Afterall, we are testing if the Greeter indeed moves back on a
169 // rollback decision, not the drag evaluation itself.
170 dragEvaluator.minDragDistance = dragEvaluator.maxDragDistance / 2;
171
172 // it starts as fully shown
173 compare(greeter.x, 0);
174
175 // then we drag it a bit
176 var startX = greeter.width - 1;
177 var touchY = greeter.height / 2;
178 var dragXDelta = -(dragEvaluator.minDragDistance * 0.3);
179 touchFlick(greeter,
180 startX , touchY, // start pos
181 startX + dragXDelta, touchY, // end pos
182 true /* beginTouch */, false /* endTouch */);
183
184 // which should make it move a bit
185 tryCompareFunction(function(){return greeter.x < 0;}, true);
186
187 // then we release it
188 touchRelease(greeter, startX + dragXDelta, touchY);
189
190 // which should make it move back into its original position as it didn't move
191 // far enough to have it hidden
192 tryCompare(greeter, "x", 0);
193 }
194
195 function test_dragToHide_data() {
196 return [
197 {tag: "left", startX: greeter.width * 0.95, endX: greeter.width * 0.1, hiddenX: -greeter.width},
198 {tag: "right", startX: greeter.width * 0.1, endX: greeter.width * 0.95, hiddenX: greeter.width},
199 ];
200 }
201 function test_dragToHide(data) {
202 compare(greeter.x, 0);
203 compare(greeter.visible, true);
204 compare(greeter.shown, true);
205 compare(greeter.showProgress, 1);
206
207 touchFlick(greeter,
208 data.startX, greeter.height / 2, // start pos
209 data.endX, greeter.height / 2); // end pos
210
211 tryCompare(greeter, "x", data.hiddenX);
212 tryCompare(greeter, "visible", false);
213 tryCompare(greeter, "shown", false);
214 tryCompare(greeter, "showProgress", 0);
215 }
216
217 function test_hiddenGreeterRemainsHiddenAfterResize_data() {
218 return [
219 {tag: "left", startX: greeter.width * 0.95, endX: greeter.width * 0.1},
220 {tag: "right", startX: greeter.width * 0.1, endX: greeter.width * 0.95},
221 ];
222 }
223 function test_hiddenGreeterRemainsHiddenAfterResize(data) {
224 touchFlick(greeter,
225 data.startX, greeter.height / 2, // start pos
226 data.endX, greeter.height / 2); // end pos
227
228 tryCompare(greeter, "x", data.tag == "left" ? -greeter.width : greeter.width);
229 tryCompare(greeter, "visible", false);
230 tryCompare(greeter, "shown", false);
231 tryCompare(greeter, "showProgress", 0);
232
233 // flip dimensions to simulate an orientation change
234 greeter.width = greeterLoader.height;
235 greeter.height = greeterLoader.width;
236
237 // All properties should remain consistent
238 tryCompare(greeter, "x", data.tag == "left" ? -greeter.width : greeter.width);
239 tryCompare(greeter, "visible", false);
240 tryCompare(greeter, "shown", false);
241 tryCompare(greeter, "showProgress", 0);
242 }
243 }
244}
2450
=== added file 'tests/qmltests/Greeter/tst_WideView.qml'
--- tests/qmltests/Greeter/tst_WideView.qml 1970-01-01 00:00:00 +0000
+++ tests/qmltests/Greeter/tst_WideView.qml 2015-02-23 15:44:00 +0000
@@ -0,0 +1,519 @@
1/*
2 * Copyright 2014 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.0
18import QtTest 1.0
19import ".."
20import "../../../qml/Greeter"
21import LightDM 0.1 as LightDM
22import Ubuntu.Components 0.1
23import Unity.Test 0.1 as UT
24
25Item {
26 id: root
27 width: units.gu(120)
28 height: units.gu(80)
29
30 Binding {
31 target: LightDM.Users
32 property: "mockMode"
33 value: "full"
34 }
35
36 Row {
37 anchors.fill: parent
38 Loader {
39 id: loader
40 width: root.width - controls.width
41 height: parent.height
42
43 property bool itemDestroyed: false
44 sourceComponent: Component {
45 WideView {
46 id: view
47
48 background: Qt.resolvedUrl("../../../qml/graphics/tablet_background.jpg")
49 userModel: LightDM.Users
50 infographicModel: LightDM.Infographic
51
52 launcherOffset: parseFloat(launcherOffsetField.text)
53 currentIndex: parseInt(currentIndexField.text, 10)
54 delayMinutes: parseInt(delayMinutesField.text, 10)
55 backgroundTopMargin: parseFloat(backgroundTopMarginField.text)
56 locked: lockedCheckBox.checked
57 inputMethod: fakeInputMethod
58
59 Component.onDestruction: {
60 loader.itemDestroyed = true
61 }
62
63 onSelected: {
64 currentIndexField.text = index;
65 }
66
67 QtObject {
68 id: fakeInputMethod
69 property bool visible: fakeKeyboard.visible
70 property var keyboardRectangle: QtObject {
71 property real x: fakeKeyboard.x
72 property real y: fakeKeyboard.y
73 property real width: fakeKeyboard.width
74 property real height: fakeKeyboard.height
75 }
76 }
77
78 Rectangle {
79 id: fakeKeyboard
80 color: "green"
81 opacity: 0.7
82 anchors.bottom: view.bottom
83 width: view.width
84 height: view.height * 0.6
85 visible: keyboardVisibleCheckBox.checked
86 Text {
87 text: "Keyboard Rectangle"
88 color: "yellow"
89 font.bold: true
90 fontSizeMode: Text.Fit
91 minimumPixelSize: 10; font.pixelSize: 200
92 verticalAlignment: Text.AlignVCenter
93 x: (parent.width - width) / 2
94 y: (parent.height - height) / 2
95 width: parent.width
96 height: parent.height
97 }
98 }
99
100 }
101 }
102 }
103
104 Rectangle {
105 id: controls
106 color: "white"
107 width: units.gu(40)
108 height: parent.height
109
110 Column {
111 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
112 spacing: units.gu(1)
113
114 Row {
115 Button {
116 text: "Show Last Chance"
117 onClicked: loader.item.showLastChance()
118 }
119 }
120 Row {
121 Button {
122 text: "Hide"
123 onClicked: loader.item.hide()
124 }
125 }
126 Row {
127 Button {
128 text: "Reset"
129 onClicked: loader.item.reset()
130 }
131 }
132 Row {
133 Button {
134 text: "Show Message"
135 onClicked: loader.item.showMessage(messageField.text)
136 }
137 TextField {
138 id: messageField
139 width: units.gu(10)
140 text: ""
141 }
142 }
143 Row {
144 Button {
145 text: "Show Prompt"
146 onClicked: loader.item.showPrompt(promptField.text, isSecretCheckBox.checked, isDefaultPromptCheckBox.checked)
147 }
148 TextField {
149 id: promptField
150 width: units.gu(10)
151 text: ""
152 }
153 CheckBox {
154 id: isSecretCheckBox
155 }
156 Label {
157 text: "secret"
158 }
159 CheckBox {
160 id: isDefaultPromptCheckBox
161 }
162 Label {
163 text: "default"
164 }
165 }
166 Row {
167 Button {
168 text: "Authenticated"
169 onClicked: {
170 if (successCheckBox.checked) {
171 loader.item.notifyAuthenticationSucceeded();
172 } else {
173 loader.item.notifyAuthenticationFailed();
174 }
175 }
176 }
177 CheckBox {
178 id: successCheckBox
179 }
180 Label {
181 text: "success"
182 }
183 }
184 Row {
185 Button {
186 text: "Try To Unlock"
187 onClicked: loader.item.tryToUnlock(toTheRightCheckBox.checked)
188 }
189 CheckBox {
190 id: toTheRightCheckBox
191 }
192 Label {
193 text: "toTheRight"
194 }
195 }
196 Row {
197 TextField {
198 id: launcherOffsetField
199 width: units.gu(10)
200 text: "0"
201 }
202 Label {
203 text: "launcherOffset"
204 }
205 }
206 Row {
207 TextField {
208 id: currentIndexField
209 width: units.gu(10)
210 text: "0"
211 }
212 Label {
213 text: "currentIndex"
214 }
215 }
216 Row {
217 TextField {
218 id: delayMinutesField
219 width: units.gu(10)
220 text: "0"
221 }
222 Label {
223 text: "delayMinutes"
224 }
225 }
226 Row {
227 TextField {
228 id: backgroundTopMarginField
229 width: units.gu(10)
230 text: "0"
231 }
232 Label {
233 text: "backgroundTopMargin"
234 }
235 }
236 Row {
237 CheckBox {
238 id: lockedCheckBox
239 }
240 Label {
241 text: "locked"
242 }
243 }
244 Row {
245 Label {
246 text: "selected: " + selectedSpy.count
247 }
248 }
249 Row {
250 Label {
251 text: "responded: " + respondedSpy.count
252 }
253 }
254 Row {
255 Label {
256 text: "teased: " + teaseSpy.count
257 }
258 }
259 Row {
260 Label {
261 text: "emergency: " + emergencySpy.count
262 }
263 }
264 Row {
265 Button {
266 text: "Reload View"
267 onClicked: {
268 loader.active = false;
269 loader.active = true;
270 }
271 }
272 }
273 Row {
274 CheckBox {
275 id: keyboardVisibleCheckBox
276 }
277 Label {
278 text: "Keyboard Visible"
279 }
280 }
281 }
282 }
283 }
284
285 SignalSpy {
286 id: selectedSpy
287 target: loader.item
288 signalName: "selected"
289 }
290
291 SignalSpy {
292 id: respondedSpy
293 target: loader.item
294 signalName: "responded"
295 }
296
297 SignalSpy {
298 id: teaseSpy
299 target: loader.item
300 signalName: "tease"
301 }
302
303 SignalSpy {
304 id: emergencySpy
305 target: loader.item
306 signalName: "emergencyCall"
307 }
308
309 UT.UnityTestCase {
310 name: "WideView"
311 when: windowShown
312
313 property Item view: loader.status === Loader.Ready ? loader.item : null
314
315 function init() {
316 view.currentIndex = 0; // break binding with text field
317 selectedSpy.clear();
318 respondedSpy.clear();
319 teaseSpy.clear();
320 emergencySpy.clear();
321 }
322
323 function cleanup() {
324 keyboardVisibleCheckBox.checked = false;
325
326 loader.itemDestroyed = false;
327 loader.active = false;
328 tryCompare(loader, "status", Loader.Null);
329 tryCompare(loader, "item", null);
330 tryCompare(loader, "itemDestroyed", true);
331 loader.active = true;
332 tryCompare(loader, "status", Loader.Ready);
333 removeTimeConstraintsFromDirectionalDragAreas(loader.item);
334 }
335
336 function getIndexOf(name) {
337 for (var i = 0; i < LightDM.Users.count; i++) {
338 if (name === LightDM.Users.data(i, LightDM.UserRoles.NameRole)) {
339 return i;
340 }
341 }
342 fail("Didn't find name")
343 return -1;
344 }
345
346 function selectUser(name) {
347 var i = getIndexOf(name);
348 view.currentIndex = i;
349 return i;
350 }
351
352 function swipeAwayCover() {
353 tryCompare(view, "fullyShown", true);
354 var touchY = view.height / 2;
355 touchFlick(view, view.width, touchY, 0, touchY);
356 var coverPage = findChild(view, "coverPage");
357 tryCompare(coverPage, "showProgress", 0);
358 waitForRendering(view);
359 }
360
361 function test_tease_data() {
362 return [
363 {tag: "locked", x: 0, offset: 0, count: 0, locked: true},
364 {tag: "left", x: 0, offset: 0, count: 1, locked: false},
365 {tag: "leftWithOffsetPass", x: 10, offset: 10, count: 1, locked: false},
366 {tag: "leftWithOffsetFail", x: 9, offset: 10, count: 0, locked: false},
367 {tag: "right", x: view.width, offset: 0, count: 1, locked: false},
368 ]
369 }
370 function test_tease(data) {
371 view.locked = data.locked;
372 view.dragHandleLeftMargin = data.offset;
373 tap(view, data.x, 0);
374 compare(teaseSpy.count, data.count);
375 }
376
377 function test_selected() {
378 var delegate = findChild(view, "username2");
379 tap(delegate);
380 compare(selectedSpy.count, 1);
381 compare(selectedSpy.signalArguments[0][0], 2);
382 compare(view.currentIndex, 0); // confirm we didn't change
383 }
384
385 function test_respondedWithPassword() {
386 view.locked = true;
387 view.showPrompt("Prompt", true, true);
388 var passwordInput = findChild(view, "passwordInput");
389 compare(passwordInput.placeholderText, "Prompt");
390 compare(passwordInput.echoMode, TextInput.Password);
391 tap(passwordInput);
392 typeString("password");
393 keyClick(Qt.Key_Enter);
394 compare(respondedSpy.count, 1);
395 compare(respondedSpy.signalArguments[0][0], "password");
396 }
397
398 function test_respondedWithNonSecret() {
399 view.locked = true;
400 view.showPrompt("otp", false, false);
401 var passwordInput = findChild(view, "passwordInput");
402 compare(passwordInput.placeholderText, "otp");
403 compare(passwordInput.echoMode, TextInput.Normal);
404 tap(passwordInput);
405 typeString("foo");
406 keyClick(Qt.Key_Enter);
407 compare(respondedSpy.count, 1);
408 compare(respondedSpy.signalArguments[0][0], "foo");
409 }
410
411 function test_respondedWithSwipe() {
412 swipeAwayCover();
413 compare(respondedSpy.count, 1);
414 compare(respondedSpy.signalArguments[0][0], "");
415 }
416
417 function test_fullyShown() {
418 tryCompare(view, "fullyShown", true);
419 swipeAwayCover();
420 tryCompare(view, "fullyShown", false);
421 }
422
423 function test_required() {
424 tryCompare(view, "required", true);
425 swipeAwayCover();
426 tryCompare(view, "required", false);
427 }
428
429 function test_showMessage() {
430 view.showMessage("Welcome to Unity Greeter");
431 view.showMessage("<font color=\"#df382c\">This is an error</font>");
432 view.showMessage("You should have seen three messages and this is a really long message too. wow so long much length");
433 var infoLabel = findChild(view, "infoLabel");
434 compare(infoLabel.text, "Welcome to Unity Greeter<br><font color=\"#df382c\">This is an error</font><br>You should have seen three messages and this is a really long message too. wow so long much length");
435 compare(infoLabel.textFormat, Text.StyledText);
436 compare(infoLabel.clip, true);
437 verify(infoLabel.contentWidth > infoLabel.width);
438 verify(infoLabel.opacity < 1);
439 tryCompare(infoLabel, "opacity", 1);
440 }
441
442 // Escape is used to reset the authentication, especially if PAM is unresponsive
443 function test_escape() {
444 view.currentIndex = 1;
445 view.locked = true;
446 view.showPrompt("Prompt", true, true);
447 var passwordInput = findChild(view, "passwordInput");
448 tap(passwordInput);
449 compare(passwordInput.focus, true);
450 compare(passwordInput.enabled, true);
451
452 typeString("password");
453 keyClick(Qt.Key_Enter);
454 compare(passwordInput.focus, false);
455 compare(passwordInput.enabled, false);
456
457 compare(selectedSpy.count, 0);
458 keyClick(Qt.Key_Escape);
459 compare(selectedSpy.count, 1);
460 compare(selectedSpy.signalArguments[0][0], 1);
461
462 view.reset();
463 compare(passwordInput.focus, false);
464 compare(passwordInput.enabled, true);
465 }
466
467 function test_unicode() {
468 var index = selectUser("unicode");
469 var label = findChild(view, "username" + index);
470 tryCompare(label, "text", "가나다라마");
471 }
472
473 function test_longName() {
474 var index = selectUser("long-name");
475 var label = findChild(view, "username" + index);
476 tryCompare(label, "truncated", true);
477 }
478
479 function test_promptless() {
480 var passwordInput = findChild(view, "passwordInput");
481
482 view.locked = true;
483 compare(passwordInput.placeholderText, "Retry");
484 tap(passwordInput);
485 compare(respondedSpy.count, 0);
486 compare(selectedSpy.count, 1);
487 compare(selectedSpy.signalArguments[0][0], 0);
488 selectedSpy.clear();
489
490 view.locked = false;
491 compare(passwordInput.placeholderText, "Tap to unlock");
492 tap(passwordInput);
493 compare(selectedSpy.count, 0);
494 compare(respondedSpy.count, 1);
495 compare(respondedSpy.signalArguments[0][0], "");
496 }
497
498 function test_loginListNotCoveredByKeyboard() {
499 var loginList = findChild(view, "loginList");
500 compare(loginList.height, view.height);
501
502 // when the vkb shows up, loginList is moved up to remain fully uncovered
503
504 keyboardVisibleCheckBox.checked = true;
505
506 tryCompare(loginList, "height", view.height - view.inputMethod.keyboardRectangle.height);
507 tryCompareFunction( function() {
508 var loginListRect = loginList.mapToItem(view, 0, 0, loginList.width, loginList.height);
509 return loginListRect.y + loginListRect.height <= view.inputMethod.keyboardRectangle.y;
510 }, true);
511
512 // once the vkb goes away, loginList goes back to its full height
513
514 keyboardVisibleCheckBox.checked = false;
515
516 tryCompare(loginList, "height", view.height);
517 }
518 }
519}
0520
=== modified file 'tests/qmltests/Tutorial/tst_Tutorial.qml'
--- tests/qmltests/Tutorial/tst_Tutorial.qml 2015-02-05 20:20:28 +0000
+++ tests/qmltests/Tutorial/tst_Tutorial.qml 2015-02-23 15:44:00 +0000
@@ -112,9 +112,9 @@
112112
113 function init() {113 function init() {
114 tryCompare(shell, "enabled", true); // enabled by greeter when ready114 tryCompare(shell, "enabled", true); // enabled by greeter when ready
115 swipeAwayGreeter();
116 AccountsService.demoEdges = false;115 AccountsService.demoEdges = false;
117 AccountsService.demoEdges = true;116 AccountsService.demoEdges = true;
117 swipeAwayGreeter();
118 }118 }
119119
120 function cleanup() {120 function cleanup() {
@@ -151,13 +151,14 @@
151 }151 }
152152
153 function swipeAwayGreeter() {153 function swipeAwayGreeter() {
154 var greeter = findChild(shell, "greeter");154 var coverPage = findChild(shell, "coverPage");
155 tryCompare(greeter, "showProgress", 1);155 tryCompare(coverPage, "showProgress", 1);
156156
157 touchFlick(shell, halfWidth, halfHeight, shell.width, halfHeight);157 touchFlick(shell, halfWidth, halfHeight, shell.width, halfHeight);
158158
159 // wait until the animation has finished159 // wait until the animation has finished
160 tryCompare(greeter, "showProgress", 0);160 var greeter = findChild(shell, "greeter");
161 tryCompare(greeter, "required", false);
161 waitForRendering(greeter);162 waitForRendering(greeter);
162 }163 }
163164
164165
=== modified file 'tests/qmltests/tst_Shell.qml'
--- tests/qmltests/tst_Shell.qml 2015-02-11 17:12:49 +0000
+++ tests/qmltests/tst_Shell.qml 2015-02-23 15:44:00 +0000
@@ -19,9 +19,11 @@
1919
20import QtQuick 2.020import QtQuick 2.0
21import QtTest 1.021import QtTest 1.0
22import AccountsService 0.1
22import GSettings 1.023import GSettings 1.0
23import LightDM 0.1 as LightDM24import LightDM 0.1 as LightDM
24import Ubuntu.Components 1.125import Ubuntu.Components 1.1
26import Ubuntu.Components.ListItems 1.0 as ListItem
25import Ubuntu.Telephony 0.1 as Telephony27import Ubuntu.Telephony 0.1 as Telephony
26import Unity.Application 0.128import Unity.Application 0.1
27import Unity.Connectivity 0.129import Unity.Connectivity 0.1
@@ -80,7 +82,7 @@
80 }82 }
8183
82 Rectangle {84 Rectangle {
83 color: "white"85 color: "darkgrey"
84 width: units.gu(30)86 width: units.gu(30)
85 height: shellLoader.height87 height: shellLoader.height
8688
@@ -98,11 +100,23 @@
98100
99 var greeter = testCase.findChild(shellLoader.item, "greeter");101 var greeter = testCase.findChild(shellLoader.item, "greeter");
100 if (!greeter.shown) {102 if (!greeter.shown) {
101 greeter.show();103 LightDM.Greeter.showGreeter();
102 }104 }
103 }105 }
104 }106 }
105 }107 }
108 ListItem.ItemSelector {
109 anchors { left: parent.left; right: parent.right }
110 activeFocusOnPress: false
111 text: "LightDM mock mode"
112 model: ["single", "single-passphrase", "single-pin"]
113 onSelectedIndexChanged: {
114 shellLoader.active = false;
115 LightDM.Greeter.mockMode = model[selectedIndex];
116 LightDM.Users.mockMode = model[selectedIndex];
117 shellLoader.active = true;
118 }
119 }
106 }120 }
107 }121 }
108 }122 }
@@ -284,11 +298,13 @@
284298
285 function test_leftEdgeDrag_data() {299 function test_leftEdgeDrag_data() {
286 return [300 return [
287 {tag: "without launcher", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true},301 {tag: "without launcher", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true, greeterShown: false},
288 {tag: "with launcher", revealLauncher: true, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true},302 {tag: "with launcher", revealLauncher: true, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true, greeterShown: false},
289 {tag: "small swipe", revealLauncher: false, swipeLength: units.gu(25), appHides: false, focusedApp: "dialer-app", launcherHides: false},303 {tag: "small swipe", revealLauncher: false, swipeLength: units.gu(25), appHides: false, focusedApp: "dialer-app", launcherHides: false, greeterShown: false},
290 {tag: "long swipe", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true},304 {tag: "long swipe", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true, greeterShown: false},
291 {tag: "long swipe", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "unity8-dash", launcherHides: false}305 {tag: "small swipe with greeter", revealLauncher: false, swipeLength: units.gu(25), appHides: false, focusedApp: "dialer-app", launcherHides: false, greeterShown: true},
306 {tag: "long swipe with greeter", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true, greeterShown: true},
307 {tag: "swipe over dash", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "unity8-dash", launcherHides: false, greeterShown: false},
292 ];308 ];
293 }309 }
294310
@@ -299,6 +315,12 @@
299 ApplicationManager.focusApplication(data.focusedApp)315 ApplicationManager.focusApplication(data.focusedApp)
300 waitUntilApplicationWindowIsFullyVisible();316 waitUntilApplicationWindowIsFullyVisible();
301317
318 var greeter = findChild(shell, "greeter");
319 if (data.greeterShown) {
320 LightDM.Greeter.showGreeter();
321 tryCompare(greeter, "fullyShown", true);
322 }
323
302 if (data.revealLauncher) {324 if (data.revealLauncher) {
303 dragLauncherIntoView();325 dragLauncherIntoView();
304 }326 }
@@ -306,8 +328,10 @@
306 swipeFromLeftEdge(data.swipeLength);328 swipeFromLeftEdge(data.swipeLength);
307 if (data.appHides) {329 if (data.appHides) {
308 waitUntilDashIsFocused();330 waitUntilDashIsFocused();
331 tryCompare(greeter, "shown", false);
309 } else {332 } else {
310 waitUntilApplicationWindowIsFullyVisible();333 waitUntilApplicationWindowIsFullyVisible();
334 compare(greeter.fullyShown, data.greeterShown);
311 }335 }
312336
313 var launcher = findChild(shell, "launcherPanel");337 var launcher = findChild(shell, "launcherPanel");
@@ -335,20 +359,20 @@
335 // Suspend while call is active...359 // Suspend while call is active...
336 callManager.foregroundCall = phoneCall;360 callManager.foregroundCall = phoneCall;
337 Powerd.status = Powerd.Off;361 Powerd.status = Powerd.Off;
338 tryCompare(greeter, "showProgress", 0);362 tryCompare(greeter, "shown", false);
339363
340 // Now try again after ending call364 // Now try again after ending call
341 callManager.foregroundCall = null;365 callManager.foregroundCall = null;
342 Powerd.status = Powerd.On;366 Powerd.status = Powerd.On;
343 Powerd.status = Powerd.Off;367 Powerd.status = Powerd.Off;
344 tryCompare(greeter, "showProgress", 1);368 tryCompare(greeter, "fullyShown", true);
345369
346 tryCompare(ApplicationManager, "suspended", true);370 tryCompare(ApplicationManager, "suspended", true);
347 compare(mainApp.state, ApplicationInfoInterface.Suspended);371 compare(mainApp.state, ApplicationInfoInterface.Suspended);
348372
349 // And wake up373 // And wake up
350 Powerd.status = Powerd.On;374 Powerd.status = Powerd.On;
351 tryCompare(greeter, "showProgress", 1);375 tryCompare(greeter, "fullyShown", true);
352376
353 // Swipe away greeter to focus app377 // Swipe away greeter to focus app
354 swipeAwayGreeter();378 swipeAwayGreeter();
@@ -359,14 +383,14 @@
359383
360 function swipeAwayGreeter() {384 function swipeAwayGreeter() {
361 var greeter = findChild(shell, "greeter");385 var greeter = findChild(shell, "greeter");
362 tryCompare(greeter, "showProgress", 1);386 tryCompare(greeter, "fullyShown", true);
363387
364 var touchX = shell.width - (shell.edgeSize / 2);388 var touchX = shell.width - (shell.edgeSize / 2);
365 var touchY = shell.height / 2;389 var touchY = shell.height / 2;
366 touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY);390 touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY);
367391
368 // wait until the animation has finished392 // wait until the animation has finished
369 tryCompare(greeter, "showProgress", 0);393 tryCompare(greeter, "shown", false);
370 waitForRendering(greeter);394 waitForRendering(greeter);
371 }395 }
372396
@@ -608,12 +632,12 @@
608632
609 waitUntilDashIsFocused();633 waitUntilDashIsFocused();
610634
611 greeter.show();635 LightDM.Greeter.showGreeter();
612 tryCompare(greeter, "showProgress", 1);636 tryCompare(greeter, "fullyShown", true);
613637
614 // The main point of this test638 // The main point of this test
615 ApplicationManager.requestFocusApplication("dialer-app");639 ApplicationManager.requestFocusApplication("dialer-app");
616 tryCompare(greeter, "showProgress", 0);640 tryCompare(greeter, "shown", false);
617 waitForRendering(greeter);641 waitForRendering(greeter);
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches