Merge lp:~mterry/unity/phablet-greeter-lightdm into lp:unity/phablet

Proposed by Michael Terry
Status: Merged
Approved by: Michał Sawicz
Approved revision: no longer in the source branch.
Merged at revision: 672
Proposed branch: lp:~mterry/unity/phablet-greeter-lightdm
Merge into: lp:unity/phablet
Prerequisite: lp:~dandrader/unity/phablet_remove_fakes_from_qml
Diff against target: 1614 lines (+1159/-99)
25 files modified
Greeter/Greeter.qml (+5/-42)
Greeter/GreeterContent.qml (+3/-2)
Greeter/LoginList.qml (+45/-16)
Shell.qml (+7/-3)
debian/control (+0/-1)
debian/qml-phone-shell.install (+1/-0)
plugins/CMakeLists.txt (+1/-0)
plugins/LightDM/CMakeLists.txt (+45/-0)
plugins/LightDM/greeter.cpp (+133/-0)
plugins/LightDM/greeter.h (+70/-0)
plugins/LightDM/lightdm-greeter.cpp (+195/-0)
plugins/LightDM/lightdm-greeter.h (+105/-0)
plugins/LightDM/lightdm-usersmodel.cpp (+178/-0)
plugins/LightDM/lightdm-usersmodel.h (+67/-0)
plugins/LightDM/plugin.cpp (+47/-0)
plugins/LightDM/plugin.h (+35/-0)
plugins/LightDM/qmldir (+2/-0)
plugins/LightDM/usersmodel.cpp (+68/-0)
plugins/LightDM/usersmodel.h (+37/-0)
plugins/Utils/qsortfilterproxymodelqml.cpp (+10/-0)
plugins/Utils/qsortfilterproxymodelqml.h (+1/-0)
tests/autopilot/qml_phone_shell/tests/helpers.py (+36/-13)
tests/plugins/Utils/qsortfilterproxymodeltest.cpp (+20/-0)
tests/qmltests/CMakeLists.txt (+1/-1)
tests/qmltests/Greeter/tst_Greeter.qml (+47/-21)
To merge this branch: bzr merge lp:~mterry/unity/phablet-greeter-lightdm
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Michał Sawicz Approve
Albert Astals Cid (community) Needs Information
Michael Zanetti Pending
Review via email: mp+159846@code.launchpad.net

This proposal supersedes a proposal from 2013-04-10.

Commit message

Add LightDM plugin that currently only has mock contents.

Description of the change

This is the start of integrating the phablet greeter into the LightDM workflow. This branch does not expose all of the functionality of LightDM, but just replaces the basics that the demo had (no-password login, simple one-password login, user backgrounds, and demo infographics).

I added a mock liblightdm library, for use during testing. It contains all the same demo test data that we currently use.

Actual integration with LightDM is only half-functional right now. Not only is a subset of LightDM's features supported (which is fine, since the phablet doesn't need to support them all), but we can't even communicate with LightDM under normal circumstances, because the greeter isn't yet a separate process. I will fix that with a future branch.

There is one feature in the mock library that is not currently even in liblightdm-qt5: the infographics. For now, I added support in the mock library for the demo infographics. But we'll have to decide how we really want to support those down the road.

To post a comment you must log in.
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
Michał Sawicz (saviq) wrote : Posted in a previous version of this proposal

Hey:

> Text conflict in Greeter/Greeter.qml
> Text conflict in run
> 2 conflicts encountered.

review: Needs Fixing
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
Michael Terry (mterry) wrote : Posted in a previous version of this proposal

I've fixed conflict, please review again, thanks!

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
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

- Breaks autopilot tests (tries to call "env -testability" instead of "qml-phone-shell -testability"
- When I run qml-phone-shell manually, it seems to make use of my real login data from LightDM, which is cool. However, it doesn't work. Neither the correct nor a wrong password have any effect.
- I have a user where I don't have filled in all the details like "Real name". Instead of just showing the username it adds an empty (invisible) item to the list.

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

mzanetti:

1) I just now fixed the env/autopilot issue.

2) The ./run script correctly sets LD_LIBRARY_PATH to point at the mock data. If you are running it manually, please use the ./run script.

3) I've updated the branch to fix the empty real name issue (and added a test). Note that this branch is missing a whole lot of features in the multi-user case. It's meant as a starting point, not full functionality.

Please review again, thanks!

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
Michael Terry (mterry) wrote : Posted in a previous version of this proposal

Just for context, it's expected that when running against your normal user data, logging in won't work. That's because it can't talk to the lightdm daemon (because the lightdm daemon didn't spawn it).

A future branch will effect the process split so that we can run against the system lightdm daemon. Though it still won't work for a casual "let me launch the greeter and try logging in" -- you'll still need to install it to the system and then test against lightdm.

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: Approve (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote : Posted in a previous version of this proposal

Can you please also add liblightdm-qt5-2-dev to the ./build script?

review: Needs Fixing
Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> Can you please also add liblightdm-qt5-2-dev to the ./build script?

same for run_on_device -s

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

1447 - wait(0) // spin event loop to start any pending animations

This needs to stay. Otherwise, every once in a while on Jenkins, the test will be faster than the animation starting which messes up with the following expectations.

review: Needs Fixing
Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

378 +QString
379 +Greeter::authenticationUser() const

Btw... this is not Qt'ish... Qt coding style (which we follow) has return values on the same line.

review: Needs Fixing
Revision history for this message
Albert Astals Cid (aacid) wrote : Posted in a previous version of this proposal

Actually for ./run_on_device we depend on build-dep (which breaks sometimes), so no need to add the dependency unless we decide to move ./run_on_device to do the same ./build does

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

582 + Q_DISABLE_COPY(LightDM)

QObjects have a private copy ctor by default

972 + roles[1000] = "infographic"; // WARNING: Not a LightDM role

Please define an enum with Qt:UserRole + 1000 or something.

1073 + engine->rootContext()->setContextProperty("lightDM", &LightDM::instance());

I guess LightDM::instance() returns a pointer. Do we really need a reference to a pointer here?

1257 + env LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/plugins/LightDM/mock

hmm... not sure we really need this. seems fishy... also mocks should live in /tests somehere and not within the real plugins.

review: Needs Fixing
Revision history for this message
Albert Astals Cid (aacid) wrote : Posted in a previous version of this proposal

Doesn't merge cleanly anymore

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

> Can you please also add liblightdm-qt5-2-dev to the ./build script?

Fixed.

> wait(0) // spin event loop to start any pending animations

Shouldn't this be in all the greeter tests then, instead of just one? And shouldn't the following 'tryCompare(passwordInput, "opacity", 1)' be enough of a wait?

Regardless, added it back.

> Qt coding style (which we follow) has return values on the same line.

Fixed. I got fooled by the coding style of the Unity plugin, which doesn't do this.

> QObjects have a private copy ctor by default

Fixed. I think I had copy-pasted that header from LightDM. So what does Q_DISABLE_COPY do then?

> Please define an enum with Qt:UserRole + 1000 or something.

Fixed, added a define for it. Can't use a straight enum, since that class doesn't have its own header (uses the system header from liblightdm).

> I guess LightDM::instance() returns a pointer. Do we really need a reference to a pointer here?

No, it returns a ref instance. Is that not-compliant with current style?

> hmm... not sure we really need this. seems fishy... also mocks should live in /tests somehere and not within the real plugins.

We do need it, for the qmluitests to be able to use a known data set. I've moved the mock library to /tests/mocks

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: Approve (continuous-integration)
Revision history for this message
Michael Terry (mterry) wrote : Posted in a previous version of this proposal

Hmm.. This will break the tablet images because no one can log in, eh?

I should add a line to always point at the mock even during real runs until my split branch lands, and we can have a proper LightDM session.

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

> > wait(0) // spin event loop to start any pending animations
>
> Shouldn't this be in all the greeter tests then, instead of just one? And
> shouldn't the following 'tryCompare(passwordInput, "opacity", 1)' be enough of
> a wait?

This is just to make sure the animation has actually started before the tryCompare() jumps in to wait for it to be finished. Otherwise the tryCompare could do the check before it even started and pass immediately.

> > QObjects have a private copy ctor by default
>
> Fixed. I think I had copy-pasted that header from LightDM. So what does
> Q_DISABLE_COPY do then?

you can use this to disable copying of non-QObjects, like QGadgets, QStrings and other classes not inheriting QObject. Besides, this is what is used inside the Q_OBJECT macro.

> > Please define an enum with Qt:UserRole + 1000 or something.
>
> Fixed, added a define for it. Can't use a straight enum, since that class
> doesn't have its own header (uses the system header from liblightdm).

Not totally happy with defines, but I guess better than random numbers. Ok. fine with me.

>
> > I guess LightDM::instance() returns a pointer. Do we really need a reference
> to a pointer here?
>
> No, it returns a ref instance. Is that not-compliant with current style?

There are various reasons for not returning refs. The main ones are:
- make it easier to keep binary compatibility. (does not really apply here - mentioning it for completeness)
- Readability/Consistency. Qt's data structures are all implicitly shared. Returning copies is cheap, so in Qt we either return a Pointer or a copy.

In general you will not find that all over the Qt codebase. As you can see what happened with me, it confuses long-term Qt people too. If possible I'd prefer to have instance() returning a pointer. (Sorry for noticing that late)

>
> > hmm... not sure we really need this. seems fishy... also mocks should live
> in /tests somehere and not within the real plugins.
>
> We do need it, for the qmluitests to be able to use a known data set. I've
> moved the mock library to /tests/mocks

Thanks.

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

> Hmm.. This will break the tablet images because no one can log in, eh?
>
> I should add a line to always point at the mock even during real runs until my
> split branch lands, and we can have a proper LightDM session.

Wouldn't there be a user phablet with password phablet?

Anyways, I agree better using the mocks until it works for real.

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

I made instance() return a pointer and made the shell always use the mock library, even when running on the real system.

To do this, I just made the liblightdm-qt5-2 library be static and included it in the LightDM plugin. This means that there are a few pointless LD_LIBRARY_PATH statements, but they will become useful as soon as the binary is split out again.

I've added TODO lines in the CMakeLists.txt to document that the library will not be static forever.

> Wouldn't there be a user phablet with password phablet?
>
> Anyways, I agree better using the mocks until it works for real.

The tablet might have a user phablet with password phablet, but you wouldn't be able to actually login as that user until we are a separate process.

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
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

ok. looks good now. Just a few merge conflicts and we can land it.

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
Michał Sawicz (saviq) wrote : Posted in a previous version of this proposal

Sorry I came so late to this review, I have some remarks:

===========

284 + env LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks

304 -Architecture: all
305 +Architecture: any

315 +usr/lib/qml-phone-shell/mocks

836 +export LD_LIBRARY_PATH=$PWD/../unity_build/build/lib:$PWD/builddir/tests/mocks

860 + oldlibpath = os.environ.get('LD_LIBRARY_PATH', '')
861 + libpath = os.path.abspath('../../builddir/tests/mocks') + ':' + oldlibpath
862 + os.environ['LD_LIBRARY_PATH'] = libpath

865 + os.environ['LD_LIBRARY_PATH'] = oldlibpath

868 + oldlibpath = os.environ.get('LD_LIBRARY_PATH', '')
869 + libpath = '/usr/lib/qml-phone-shell/mocks:' + oldlibpath
870 + os.environ['LD_LIBRARY_PATH'] = libpath

877 + os.environ['LD_LIBRARY_PATH'] = oldlibpath

Please don't do that, we've built a way of using mock plugins in lieu of missing real implementations in soon-to-be-merged lp:~dandrader/unity/phablet_remove_fakes_from_qml. You just build your plugin in /tests/mocks and install it in ${SHELL_APP_DIR}/plugins/mocks - it will get picked up as fall back. Because this is still needed in the device images, please package with qml-phone-shell, not -fake-env like the fake Ubuntu.Application is.

===========

658 + qmlRegisterUncreatableType<LightDM>(uri, 0, 1, "LightDM", "Type is not instantiable");

665 + engine->rootContext()->setContextProperty("lightDM", LightDM::instance());

Please use registerSingletonType() instead [1].

===========

397 +#include <QLightDM/Greeter>
398 +#include "greeter.h"

739 +#include <QSortFilterProxyModel>
740 +#include "usersmodel.h"

Please include self as the first one and use <QtCore/QSortFilterProxyModel>.

===========

488 +class Q_DECL_EXPORT Greeter : public QObject

Q_DECL_EXPORT shouldn't be needed, it's a plugin not a shared lib?

===========

742 +// First, we define an internal class that wraps LightDM's UsersModel. This
743 +// class will modify some of the data coming from LightDM. For example, we
744 +// modify any empty Real Names into just normal Names.

Why is that? Why not do the mangling straight in the UsersModel and avoid the wrapping?

===========

957 +# Link to liblightdm's headers for moc's sake
958 +execute_process(COMMAND ln -sf "${LIBLIGHTDM_INCLUDEDIR}/lightdm-qt5-2/QLightDM/greeter.h" "${CMAKE_CURRENT_SOURCE_DIR}/lightdm-greeter.h"
959 + COMMAND ln -sf "${LIBLIGHTDM_INCLUDEDIR}/lightdm-qt5-2/QLightDM/usersmodel.h" "${CMAKE_CURRENT_SOURCE_DIR}/lightdm-usersmodel.h"
960 + )

1181 +#include "moc_lightdm-greeter.cpp"

1364 +#include "moc_lightdm-usersmodel.cpp"

No need for those - automoc seemed to work just fine here without all that?

===========

Packaging is bad:

cp: cannot stat `debian/tmp/usr/lib/qml-phone-shell/mocks': No such file or directory

===========

Again, sorry for the late review, but we should be really close...

[1] http://qt-project.org/doc/qt-5.0/qtqml/qqmlengine.html#qmlRegisterSingletonType

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

As for the remove_fakes_from_qml branch, I merged it in (so it is now a prerequisite of this branch), but I'm actually going a different route for this first round. Until we separate out the greeter process, I'm just building the mock into the real plugin. Once we have a separate process, we can use both a real and a mock plugin.

> Please use registerSingletonType() instead [1].

Done.

> Please include self as the first one and use <QtCore/QSortFilterProxyModel>.

Done.

> Q_DECL_EXPORT shouldn't be needed, it's a plugin not a shared lib?

Dropped.

> Why is that? Why not do the mangling straight in the UsersModel and avoid the wrapping?

I added a comment to the source to explain why. It's because the QSortFilterProxyModel does not use the data() function to sort, but rather goes directly to its source model. So when we are mangling real names, it would not sort on the resulting name like we want.

> No need for those - automoc seemed to work just fine here without all that?

It will build, but will not run due to missing QObject meta symbols.

> cp: cannot stat `debian/tmp/usr/lib/qml-phone-shell/mocks': No such file or directory

Fixed.

Thanks for the reviews, everyone. I'm very rusty on Qt and our project conventions.

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
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
Albert Astals Cid (aacid) wrote :

/tmp/buildd/qml-phone-shell-1.71/plugins/LightDM/lightdm-greeter.h: bad whitespace in lines 35, 45, 64

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

> /tmp/buildd/qml-phone-shell-1.71/plugins/LightDM/lightdm-greeter.h: bad whitespace in lines 35, 45, 64

Fixed (as well as another conflict with trunk). Sorry I missed this, but apparently "alltests" doesn't run the whitespace test anymore? I had to run the "test" target to get those tests.

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
Albert Astals Cid (aacid) wrote :

Text conflict in tests/qmltests/CMakeLists.txt

review: Needs Fixing
Revision history for this message
Michał Sawicz (saviq) wrote :

Please remerge...

review: Needs Fixing
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: Approve (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: Approve (continuous-integration)
Revision history for this message
Michał Sawicz (saviq) wrote :

This introduces the following warnings:
file:///home/phablet/shell/Shell.qml:125:9: QML QQuickImage: Cannot open: file:///home/phablet/shell/*
file:///home/phablet/shell/Shell.qml:125:9: QML QQuickImage: Cannot open: file:///home/phablet/shell/*
file:///home/phablet/shell/Components/CrossFadeImage.qml:102:5: QML QQuickImage: Cannot open: file:///home/phablet/shell/*

review: Needs Fixing
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote :

Do we really need the files copied from liblightdm? Can't we use the library or something?

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

Albert, there are two relevant branches for LightDM.

This one gets us to a point where we are using a LightDM-like API. The other [1] will get us to the point where we actually use LightDM.

We can't merge the second until we are ready to use LightDM on the phablet image, which is blocked on Mir being used on the phablet image. But this branch can be merged now.

As for copying files from liblightdm, we are only copying a couple headers for use in the mock. In this branch, the LightDM plugin is really one big mock. In the split branch [1], there is both a real LightDM plugin and a mock one. But this branch just has the mock one which is used as if it were a real one.

[1] https://code.launchpad.net/~mterry/unity/phablet-split/+merge/161516

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

Works for me if we accept the fake lightdm plugin. Michael? Michał? What do you think?

review: Approve
Revision history for this message
Michał Sawicz (saviq) wrote :

Generally, why wouldn't the Qt plugin be part of liblightdm-qt?

Is there a reason for the LightDM class? I'd just expose Greeter and Users as singletons directly - and you don't need the instance() method, 'cause the singleton provider function is static.

Please use a named import - "import LightDM 0.1 as YourPreferredName".

We should have a "background" and "infographic" properties instead of the selected(uid) signals and accessing the model directly - but that's unrelated, so let's leave it for now.

==========
 119 + property bool noPasswordNeeded: false

This seems like it could be a Greeter::passwordless property?

==========
 203 + if (!userList.currentItem)
 204 + return;

Please wrap all if() blocks in curly braces.

==========
 454 + void showMessage(QString text, bool isError);
 455 + void showPrompt(QString text, bool isSecret);
 456 + void authenticationComplete();

You didn't use the string provided by showPrompt(), but anyway do you think we could instead have Greeter::prompt, Greeter::promptType properties - seems more QML-ish. I'd rather have a Greeter::status and/or corresponding properties that would change accordingly instead of those signals. QML interfaces should make much more use of properties rather than signals, to facilitate property binding and declarative approach.

==========
 376 +void Greeter::respond(const QString &response)
 377 +{
 378 + m_greeter->respond(response);
 379 +}

The Greeter::respond name is weird, could we maybe have a better name here? Or at least explain what's happening?

==========
Not finished yet...

review: Needs Fixing
Revision history for this message
Michał Sawicz (saviq) wrote :

OK the above is all.

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

> Generally, why wouldn't the Qt plugin be part of liblightdm-qt?

Because not all potential Qt greeters are the same. liblightdm-qt provides the bare data, this Qml plugin mangles that data in certain ways (sorts it by realName, strips colons from prompt strings, modifies realName to be name if empty, etc) that other Qt greeters may not want.

> Is there a reason for the LightDM class? I'd just expose
> Greeter and Users as singletons directly - and you don't
> need the instance() method, 'cause the singleton provider
> function is static.

The only reason was as a namespace. I'm now exposing Greeters and Users as singletons and have dropped the LightDM class.

> Please use a named import - "import LightDM 0.1 as YourPreferredName".

Ah, which provides the namespace we lose from above. Why is the named import the preferred way to do that? I don't see many other named imports in the code.

> This seems like it could be a Greeter::passwordless property?

Done. I've not moved this field into the plugin directly as Greeter::promptless.

> Please wrap all if() blocks in curly braces.

Done.

> You didn't use the string provided by showPrompt()

Yes, intentionally. I didn't want to complicate this merge by adding new features or fixing bugs. Anyway, that's a multi-user feature and as such, it isn't necessary for 1.0.

> but anyway do you think we could instead have Greeter::prompt,
> Greeter::promptType properties - seems more QML-ish. I'd
> rather have a Greeter::status and/or corresponding properties
> that would change accordingly instead of those signals. QML
> interfaces should make much more use of properties rather than
> signals, to facilitate property binding and declarative approach.

Adding prompt or promptType properties is problematic because we will need to support multiple prompts in the future. So we may have 1 or more prompt boxes to show the user, potentially of different types.

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

> The Greeter::respond name is weird, could we maybe have
> a better name here? Or at least explain what's happening?

Sorry, missed this question in my previous reply.

"respond" is inherited from liblightdm's terminology. It's what happens when the user enters a password. The greeter "responds" to the system's prompt.

Revision history for this message
Michał Sawicz (saviq) wrote :

W dniu 02.05.2013 12:29, Michael Terry pisze:
>> > Please use a named import - "import LightDM 0.1 as YourPreferredName".
> Ah, which provides the namespace we lose from above. Why is the named import the preferred way to do that? I don't see many other named imports in the code.

The result is the same, but there's simply less code :), and there
should be more of them, I agree.

>> > You didn't use the string provided by showPrompt()
> Yes, intentionally. I didn't want to complicate this merge by adding new features or fixing bugs. Anyway, that's a multi-user feature and as such, it isn't necessary for 1.0.

OK.

>> > but anyway do you think we could instead have Greeter::prompt,
>> > Greeter::promptType properties - seems more QML-ish. I'd
>> > rather have a Greeter::status and/or corresponding properties
>> > that would change accordingly instead of those signals. QML
>> > interfaces should make much more use of properties rather than
>> > signals, to facilitate property binding and declarative approach.
> Adding prompt or promptType properties is problematic because we will need to support multiple prompts in the future. So we may have 1 or more prompt boxes to show the user, potentially of different types.

Yeah, we'll have to revisit when the time comes.

One last thing for now - instead of all the .get(currentIndex).*, could
we have corresponding readonly properties on the delegate and use
"userList.currentItem.background" etc?

--
Michał Sawicz <email address hidden>
Canonical Services Ltd.

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

> One last thing for now - instead of all the .get(currentIndex).*, could
> we have corresponding readonly properties on the delegate and use
> "userList.currentItem.background" etc?

Done. Except I didn't change uses outside the Greeter component (i.e. the one use in Shell.qml to get the backgroundPath. It didn't seem appropriate to expose the inner details of the userList outside Greeter.

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

Using currentItem actually caused a few problems. The prompt changed too early (before scrolling) and it had an off-by-one error (guest button would appear after switching away from guest).

This is because when you click on a user and we start authentication, the previous user is still the currentItem, but the new user is the currentIndex.

I also added some code to use the default background if no background is defined by LightDM.

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: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michael Terry (mterry) wrote :

Last week, Saviq noticed that in tablet mode, my branch made switching between users noticeably slow. It turns out this was due to the expensive get() method on our QSortFilterProxyModelQML implementation. It mapped all the role names to all the values and returned the whole mapping each time. When usually callers were just interested in one piece of data.

So I added a version that took a role enum value and returned just one point of data. This fixed the speed problems. But there are two oddities with my version that I'm hoping a Qml expert will be able to solve:

1) When I first had the get-one-role method return a QVariant, Qml treated it as an object instead of unboxing it to a native type. Is there a trick to get it to do that, like it does for QVariantMap? So I named the method getString() since that's all I needed for now and it avoided the problem.

2) I wanted to have Qml ask for a role by enum rather than role string because that would be one less lookup for the method to do. But without exposing the liblightdm object, I couldn't find a way to "proxy" the enumeration from the liblightdm UserModel class to our thin wrapper around it. Q_ENUMS can point to another class's enumeration, but it doesn't seem to expose it to Qml like I'd expect. Same for declaring a typedef in our wrapper. So I ended up just copying the enumeration.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote :

The getString seems a bit weird, i think using a roleName (as in the roleNames() function) would be the "declarative way". Saviq?

review: Needs Information
Revision history for this message
Michał Sawicz (saviq) wrote :

Generally I find this weird that QSortFilterProxyModelQML::get(int row) would be so slow, we need to fix that.

I agree with Albert that all this is non-declarative and would like to later see a way to fix that, too.

Ad 1.:
Q_INVOKABLE QVariant data(int row, int role);

QVariant
QSortFilterProxyModelQML::data(int row, int role)
{
    return index(row, 0).data(role);
}

Seems to work just fine here.

Ad 2.:
I'm generally of the opinion that enums should be wrapped in separate non-creatable classes used just for that - exposing enums. We could do that in our fake plugin, but would later require changing the QLightDM API.
For copying, I'd go with at least:

    enum UserModelRoles {NameRole = QLightDM::UsersModel::NameRole,
                         RealNameRole = QLightDM::UsersModel::RealNameRole,
                         LoggedInRole = QLightDM::UsersModel::LoggedInRole,
                         BackgroundRole = QLightDM::UsersModel::BackgroundRole,
                         SessionRole = QLightDM::UsersModel::SessionRole,
                         HasMessagesRole = QLightDM::UsersModel::HasMessagesRole,
                         ImagePathRole = QLightDM::UsersModel::ImagePathRole,
                         BackgroundPathRole = QLightDM::UsersModel::BackgroundPathRole,
                         InfographicRole = QLightDM::UsersModel::InfographicRole // WARNING: not a real lightdm role
    };

This would ensure that we're not mismatching them.

I also just found some references to greeterContentLoader inside GreeterContent.qml which we need to fix later - components should not access objects outside of their scope if possible.

Revision history for this message
Michał Sawicz (saviq) wrote :

A potentially better way to expose those enums would be to expose the original QLightDM::UsersModel in our plugin, uncreatable, under a different name.

Revision history for this message
Michał Sawicz (saviq) wrote :

A slightly (only _slightly_) more QML-friendly approach would be to go back to putting the values on the delegate properties, but react onIndexChanged and ListView::itemAt().

Revision history for this message
Michał Sawicz (saviq) wrote :

OK we know now why it was so slow - every call to get() would asynchronously create a QPixmap of the background. We've discussed that that's unacceptable and we should get rid of the get() method altogether from QSortFilterProxyModel (Albert added a workitem to a client-1303-unity-ui-dash blueprint).

Adding the data(int, int) method would be safe, but could lead to us abusing it as it adds to ListModel API and we might end up expecting that to be implemented.

The approach I mentioned in my previous comment should let us get rid of that assumption and the added method.

review: Needs Fixing
Revision history for this message
Michał Sawicz (saviq) wrote :

s/asynchronously/synchronously/

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

Since itemAt() takes an x,y pair not an index, Saviq and I agreed it's not quite suitable here.

But I did use a QVariant successfully, not sure how I got that wrong before.

And I expose liblightdm's user model object as "UserRoles" for its enum.

Should be good now. (Apart from lingering unhappiness about using role indexes at all.)

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (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
Michał Sawicz (saviq) wrote :

Hey, there's a small conflict now after we've dropped ::get(int)...

review: Needs Fixing
Revision history for this message
Michał Sawicz (saviq) wrote :

Could you also please add tests for the ::data method?

Revision history for this message
Michał Sawicz (saviq) wrote :

Otherwise it's good! Definitely too long in the making, though...

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

Fixed conflict and added test for ::data.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michał Sawicz (saviq) :
review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michał Sawicz (saviq) wrote :

Object UsersModel(0x6b4c30) has no method 'get'

review: Needs Fixing
Revision history for this message
Michał Sawicz (saviq) wrote :

You need to fix the tests to use data(), too.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michał Sawicz (saviq) wrote :

Yes!

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Greeter/Greeter.qml'
2--- Greeter/Greeter.qml 2013-04-09 00:39:33 +0000
3+++ Greeter/Greeter.qml 2013-05-13 15:26:27 +0000
4@@ -16,7 +16,7 @@
5
6 import QtQuick 2.0
7 import Ubuntu.Components 0.1
8-import QtQuick.XmlListModel 2.0
9+import LightDM 0.1 as LightDM
10 import "../Components"
11
12 Showable {
13@@ -24,8 +24,8 @@
14 enabled: shown
15 created: greeterContentLoader.status == Loader.Ready && greeterContentLoader.item.ready
16
17- property alias model: userList
18- property bool locked: shown && multiUser && !greeterContentLoader.guestAccount
19+ property alias model: greeterContentLoader.model
20+ property bool locked: shown && multiUser && !greeterContentLoader.promptless
21
22 readonly property bool narrowMode: width <= units.gu(60)
23 readonly property bool multiUser: !narrowMode // TODO: populate with real value
24@@ -33,50 +33,13 @@
25 signal selected(int uid)
26 signal unlocked(int uid)
27
28- ListModel {
29- id: userList
30- objectName: "userList"
31- ListElement { name: "Guest"; background: "graphics/tablet_background.jpg" }
32- }
33-
34- // TODO: Replace with a ModelAggregator once we have one.
35- // Check Unity2D for a possibly usable one.
36- XmlListModel {
37- id: xmlReader
38- source: "/usr/share/demo-assets/shell/users.xml"
39- query: "/users/user"
40-
41- XmlRole { name: "name"; query: "username/string()" }
42- XmlRole { name: "password"; query: "password/string()" }
43- XmlRole { name: "background"; query: "background/string()" }
44- XmlRole { name: "infographic"; query: "infographic/string()" }
45-
46- onStatusChanged: {
47- if (status == XmlListModel.Ready) {
48- // Need to reset the model to make sure everything updates correctly.
49- // Otherwise currentIndex, count etc could stay the same and bindings are not updated.
50- greeterContentLoader.currentIndex = -1;
51- greeterContentLoader.model = undefined;
52-
53- userList.clear();
54- for (var i = 0; i < xmlReader.count; i++) {
55- userList.append(xmlReader.get(i));
56- }
57- userList.append({ "name": "Guest", background: "graphics/tablet_background.jpg" });
58-
59- greeterContentLoader.model = userList;
60- greeterContentLoader.currentIndex = 0;
61- }
62- }
63- }
64-
65 Loader {
66 id: greeterContentLoader
67 objectName: "greeterContentLoader"
68 anchors.fill: parent
69- property variant model: userList
70+ property variant model: LightDM.Users
71 property int currentIndex: 0
72- property bool guestAccount: item ? item.guestAccount : false
73+ property bool promptless: item ? item.promptless : false
74
75 source: required ? "GreeterContent.qml" : ""
76
77
78=== modified file 'Greeter/GreeterContent.qml'
79--- Greeter/GreeterContent.qml 2013-04-05 11:03:11 +0000
80+++ Greeter/GreeterContent.qml 2013-05-13 15:26:27 +0000
81@@ -16,13 +16,14 @@
82
83 import QtQuick 2.0
84 import Ubuntu.Components 0.1
85+import LightDM 0.1 as LightDM
86 import "../Components"
87
88 MouseArea {
89 id: root
90 anchors.fill: parent
91
92- property bool guestAccount: loginLoader.status == Loader.Ready && loginLoader.item.guestAccount
93+ property bool promptless: loginLoader.status == Loader.Ready && LightDM.Greeter.promptless
94 property bool ready: wallpaper.status == Image.Ready
95
96 signal selected(int uid)
97@@ -109,6 +110,6 @@
98 right: narrowMode ? undefined : parent.right
99 rightMargin: narrowMode ? 0 : units.gu(-6)
100 }
101- source: narrowMode ? "graphics/infographic_tweeter_cut.png" : userList.get(greeterContentLoader.currentIndex).infographic ? userList.get(greeterContentLoader.currentIndex).infographic : ""
102+ source: narrowMode ? "graphics/infographic_tweeter_cut.png" : loginLoader.item.userList.model.data(currentIndex, LightDM.UserRoles.InfographicRole) ? loginLoader.item.userList.model.data(currentIndex, LightDM.UserRoles.InfographicRole) : ""
103 }
104 }
105
106=== modified file 'Greeter/LoginList.qml'
107--- Greeter/LoginList.qml 2013-04-12 05:54:57 +0000
108+++ Greeter/LoginList.qml 2013-05-13 15:26:27 +0000
109@@ -16,17 +16,18 @@
110
111 import QtQuick 2.0
112 import Ubuntu.Components 0.1
113+import LightDM 0.1 as LightDM
114
115 Item {
116 id: root
117
118+ property alias userList: userList
119 property alias model: userList.model
120 property alias currentIndex: userList.currentIndex
121
122 readonly property int cellHeight: units.gu(6)
123 readonly property int highlightedHeight: units.gu(10)
124 readonly property int moveDuration: 200
125- readonly property bool guestAccount: userList.model.get(userList.currentIndex).password === undefined
126
127 signal selected(int uid)
128 signal unlocked(int uid)
129@@ -66,9 +67,18 @@
130 readonly property int offsetStartDistance: units.gu(7)
131 readonly property int fadeOutDistance: pathStartMargin - units.gu(1)
132
133+ onCountChanged: {
134+ // Start authentication when we start up
135+ if (!LightDM.Greeter.authenticationUser && userList.currentItem) {
136+ root.selected(userList.currentIndex);
137+ root.resetAuthentication();
138+ }
139+ }
140+
141 onCurrentIndexChanged: {
142- passwordInput.text = "";
143- passwordInput.focus = false
144+ if (LightDM.Greeter.authenticationUser != userList.model.data(currentIndex, LightDM.UserRoles.NameRole)) {
145+ root.resetAuthentication();
146+ }
147 }
148
149 onMovingInternallyChanged: {
150@@ -157,7 +167,7 @@
151 right: parent.right
152 rightMargin: units.gu(2)
153 }
154- text: name
155+ text: realName
156 color: "white"
157 elide: Text.ElideRight
158 }
159@@ -201,7 +211,7 @@
160 }
161 height: units.gu(4.5)
162 width: parent.width - anchors.margins * 2
163- placeholderText: root.guestAccount ? "Tap to unlock" : "Password"
164+ placeholderText: LightDM.Greeter.promptless ? "Tap to unlock" : "Password"
165 echoMode: TextInput.Password
166 opacity: userList.movingInternally ? 0 : 1
167
168@@ -209,15 +219,7 @@
169 NumberAnimation { duration: 100 }
170 }
171
172- onAccepted: {
173- if (text === userList.model.get(userList.currentIndex).password) {
174- root.unlocked(userList.currentIndex);
175- passwordInput.focus = false;
176- } else {
177- wrongPasswordAnimation.start();
178- }
179- text = "";
180- }
181+ onAccepted: LightDM.Greeter.respond(text)
182
183 Image {
184 anchors {
185@@ -225,7 +227,7 @@
186 rightMargin: units.gu(2)
187 verticalCenter: parent.verticalCenter
188 }
189- visible: root.guestAccount
190+ visible: LightDM.Greeter.promptless
191 source: "graphics/icon_arrow.png"
192 }
193
194@@ -265,11 +267,38 @@
195 }
196
197 }
198+
199 MouseArea {
200 anchors.fill: passwordInput
201- enabled: root.guestAccount
202+ enabled: LightDM.Greeter.promptless
203 onClicked: {
204 root.unlocked(userList.currentIndex);
205 }
206 }
207+
208+ function resetAuthentication() {
209+ if (!userList.currentItem) {
210+ return;
211+ }
212+ passwordInput.text = "";
213+ passwordInput.focus = false;
214+ LightDM.Greeter.authenticate(userList.model.data(currentIndex, LightDM.UserRoles.NameRole));
215+ }
216+
217+ Connections {
218+ target: LightDM.Greeter
219+
220+ onAuthenticationComplete: {
221+ if (LightDM.Greeter.promptless) {
222+ return;
223+ }
224+ if (LightDM.Greeter.authenticated) {
225+ root.unlocked(userList.currentIndex);
226+ passwordInput.focus = false;
227+ } else {
228+ wrongPasswordAnimation.start();
229+ }
230+ passwordInput.text = "";
231+ }
232+ }
233 }
234
235=== modified file 'Shell.qml'
236--- Shell.qml 2013-05-02 22:07:11 +0000
237+++ Shell.qml 2013-05-13 15:26:27 +0000
238@@ -17,6 +17,7 @@
239 import QtQuick 2.0
240 import Ubuntu.Application 0.1
241 import Ubuntu.Components 0.1
242+import LightDM 0.1 as LightDM
243 import "Dash"
244 import "Greeter"
245 import "Launcher"
246@@ -36,7 +37,8 @@
247 height: tablet ? units.gu(100) : units.gu(71)
248
249 property real edgeSize: units.gu(2)
250- property url background: shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg"
251+ property url default_background: shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg"
252+ property url background: default_background
253 readonly property real panelHeight: panel.panelHeight
254
255 property bool dashShown: dash.shown
256@@ -351,8 +353,10 @@
257 onShownChanged: if (shown) greeter.forceActiveFocus()
258
259 onUnlocked: greeter.hide()
260- onSelected: shell.background = greeter.model.get(uid).background;
261-
262+ onSelected: {
263+ var bgPath = greeter.model.data(uid, LightDM.UserRoles.BackgroundPathRole)
264+ shell.background = bgPath ? bgPath : default_background
265+ }
266 }
267
268 Revealer {
269
270=== modified file 'debian/control'
271--- debian/control 2013-05-09 08:55:23 +0000
272+++ debian/control 2013-05-13 15:26:27 +0000
273@@ -4,7 +4,6 @@
274 Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
275 Build-Depends: cmake,
276 debhelper (>= 9),
277- demo-assets,
278 doxygen,
279 graphviz,
280 libboost-regex1.49-dev (>= 1.49),
281
282=== modified file 'debian/qml-phone-shell.install'
283--- debian/qml-phone-shell.install 2013-04-18 18:53:42 +0000
284+++ debian/qml-phone-shell.install 2013-05-13 15:26:27 +0000
285@@ -12,5 +12,6 @@
286 /usr/share/qml-phone-shell/SideStage/*
287 /usr/share/qml-phone-shell/graphics/*
288 /usr/share/qml-phone-shell/plugins/HudClient/*
289+/usr/share/qml-phone-shell/plugins/LightDM/*
290 /usr/share/qml-phone-shell/plugins/Unity/*
291 /usr/share/qml-phone-shell/plugins/Utils/*
292
293=== modified file 'plugins/CMakeLists.txt'
294--- plugins/CMakeLists.txt 2013-04-18 17:20:37 +0000
295+++ plugins/CMakeLists.txt 2013-05-13 15:26:27 +0000
296@@ -1,3 +1,4 @@
297 add_subdirectory(Utils)
298 add_subdirectory(Unity)
299 add_subdirectory(HudClient)
300+add_subdirectory(LightDM)
301
302=== added directory 'plugins/LightDM'
303=== added file 'plugins/LightDM/CMakeLists.txt'
304--- plugins/LightDM/CMakeLists.txt 1970-01-01 00:00:00 +0000
305+++ plugins/LightDM/CMakeLists.txt 2013-05-13 15:26:27 +0000
306@@ -0,0 +1,45 @@
307+# Dependencies
308+# TODO: Once we split out a separate greeter process, uncomment these lines
309+#include(FindPkgConfig)
310+#pkg_check_modules(LIBLIGHTDM REQUIRED liblightdm-qt5-2)
311+
312+include_directories(
313+ ${CMAKE_CURRENT_SOURCE_DIR}
314+ ${CMAKE_CURRENT_BINARY_DIR}
315+ ${LIBLIGHTDM_INCLUDE_DIRS}
316+)
317+
318+set(QMLPLUGIN_SRC
319+ ../Utils/qsortfilterproxymodelqml.cpp # FIXME evaluate a more generic approach for using other plugins
320+ greeter.cpp
321+ plugin.cpp
322+ usersmodel.cpp
323+ # TODO: Once we split out a separate greeter process, we should move these
324+ # files to a mock plugin and link this plugin to the real liblightdm
325+ lightdm-greeter.cpp
326+ lightdm-usersmodel.cpp
327+ )
328+
329+add_library(LightDM-qml MODULE
330+ ${QMLPLUGIN_SRC}
331+ )
332+
333+# TODO: Once we split out a separate greeter process, uncomment these lines
334+#target_link_libraries(LightDM-qml
335+# ${LIBLIGHTDM_LDFLAGS}
336+# )
337+
338+qt5_use_modules(LightDM-qml Gui Qml)
339+
340+# copy qmldir file into build directory for shadow builds
341+file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/qmldir"
342+ DESTINATION ${CMAKE_CURRENT_BINARY_DIR}
343+ )
344+
345+install(TARGETS LightDM-qml
346+ DESTINATION ${SHELL_APP_DIR}/plugins/LightDM
347+ )
348+
349+install(FILES qmldir
350+ DESTINATION ${SHELL_APP_DIR}/plugins/LightDM
351+ )
352
353=== added file 'plugins/LightDM/greeter.cpp'
354--- plugins/LightDM/greeter.cpp 1970-01-01 00:00:00 +0000
355+++ plugins/LightDM/greeter.cpp 2013-05-13 15:26:27 +0000
356@@ -0,0 +1,133 @@
357+/*
358+ * Copyright (C) 2013 Canonical, Ltd.
359+ *
360+ * This program is free software; you can redistribute it and/or modify
361+ * it under the terms of the GNU General Public License as published by
362+ * the Free Software Foundation; version 3.
363+ *
364+ * This program is distributed in the hope that it will be useful,
365+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
366+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
367+ * GNU General Public License for more details.
368+ *
369+ * You should have received a copy of the GNU General Public License
370+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
371+ *
372+ * Author: Michael Terry <michael.terry@canonical.com>
373+ */
374+
375+#include "greeter.h"
376+#include "lightdm-greeter.h"
377+
378+class GreeterPrivate
379+{
380+public:
381+ explicit GreeterPrivate(Greeter *parent);
382+
383+ QLightDM::Greeter *m_greeter;
384+ bool wasPrompted;
385+ bool promptless;
386+
387+protected:
388+ Greeter * const q_ptr;
389+
390+private:
391+ Q_DECLARE_PUBLIC(Greeter)
392+};
393+
394+
395+GreeterPrivate::GreeterPrivate(Greeter* parent)
396+ : m_greeter(new QLightDM::Greeter(parent)),
397+ wasPrompted(false),
398+ promptless(false),
399+ q_ptr(parent)
400+{
401+}
402+
403+Greeter::Greeter(QObject* parent)
404+ : QObject(parent),
405+ d_ptr(new GreeterPrivate(this))
406+{
407+ Q_D(Greeter);
408+
409+ connect(d->m_greeter, SIGNAL(showMessage(QString, QLightDM::Greeter::MessageType)),
410+ this, SLOT(showMessageFilter(QString, QLightDM::Greeter::MessageType)));
411+ connect(d->m_greeter, SIGNAL(showPrompt(QString, QLightDM::Greeter::PromptType)),
412+ this, SLOT(showPromptFilter(QString, QLightDM::Greeter::PromptType)));
413+ connect(d->m_greeter, SIGNAL(authenticationComplete()),
414+ this, SLOT(authenticationCompleteFilter()));
415+
416+ d->m_greeter->connectSync();
417+}
418+
419+bool Greeter::isAuthenticated() const
420+{
421+ Q_D(const Greeter);
422+ return d->m_greeter->isAuthenticated();
423+}
424+
425+QString Greeter::authenticationUser() const
426+{
427+ Q_D(const Greeter);
428+ return d->m_greeter->authenticationUser();
429+}
430+
431+bool Greeter::promptless() const
432+{
433+ Q_D(const Greeter);
434+ return d->promptless;
435+}
436+
437+void Greeter::authenticate(const QString &username)
438+{
439+ Q_D(Greeter);
440+ d->wasPrompted = false;
441+ if (d->promptless) {
442+ d->promptless = false;
443+ Q_EMIT promptlessChanged();
444+ }
445+
446+ d->m_greeter->authenticate(username);
447+}
448+
449+void Greeter::respond(const QString &response)
450+{
451+ Q_D(Greeter);
452+ d->m_greeter->respond(response);
453+}
454+
455+bool Greeter::startSessionSync(const QString &session)
456+{
457+ Q_D(Greeter);
458+ return d->m_greeter->startSessionSync(session);
459+}
460+
461+void Greeter::showPromptFilter(QString text, QLightDM::Greeter::PromptType type)
462+{
463+ Q_D(Greeter);
464+ d->wasPrompted = true;
465+
466+ // Strip prompt of any colons at the end
467+ text = text.trimmed();
468+ if (text.endsWith(":") || text.endsWith(":")) {
469+ text.chop(1);
470+ }
471+
472+ Q_EMIT showPrompt(text, type == QLightDM::Greeter::PromptTypeSecret);
473+}
474+
475+void Greeter::showMessageFilter(QString text, QLightDM::Greeter::MessageType type)
476+{
477+ Q_EMIT showMessage(text, type == QLightDM::Greeter::MessageTypeError);
478+}
479+
480+void Greeter::authenticationCompleteFilter()
481+{
482+ Q_D(Greeter);
483+ if (!d->wasPrompted) {
484+ d->promptless = true;
485+ Q_EMIT promptlessChanged();
486+ }
487+
488+ Q_EMIT authenticationComplete();
489+}
490
491=== added file 'plugins/LightDM/greeter.h'
492--- plugins/LightDM/greeter.h 1970-01-01 00:00:00 +0000
493+++ plugins/LightDM/greeter.h 2013-05-13 15:26:27 +0000
494@@ -0,0 +1,70 @@
495+/*
496+ * Copyright (C) 2012,2013 Canonical, Ltd.
497+ *
498+ * This program is free software; you can redistribute it and/or modify
499+ * it under the terms of the GNU General Public License as published by
500+ * the Free Software Foundation; version 3.
501+ *
502+ * This program is distributed in the hope that it will be useful,
503+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
504+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
505+ * GNU General Public License for more details.
506+ *
507+ * You should have received a copy of the GNU General Public License
508+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
509+ *
510+ * Authors: Michael Terry <michael.terry@canonical.com>
511+ */
512+
513+/* This class is a really tiny filter around QLightDM::Greeter. There are some
514+ operations that we want to edit a bit for the benefit of Qml. Specifically,
515+ we want to chop colons off of any password prompts. But there may be more
516+ such edits in the future, and by inserting ourselves here, we have more
517+ control. */
518+
519+#ifndef GREETER_H
520+#define GREETER_H
521+
522+#include "lightdm-greeter.h"
523+#include <QtCore/QObject>
524+
525+class GreeterPrivate;
526+
527+class Greeter : public QObject
528+{
529+ Q_OBJECT
530+
531+ Q_PROPERTY(bool authenticated READ isAuthenticated)
532+ Q_PROPERTY(QString authenticationUser READ authenticationUser)
533+ Q_PROPERTY(bool promptless READ promptless NOTIFY promptlessChanged)
534+
535+public:
536+ explicit Greeter(QObject* parent=0);
537+
538+ bool isAuthenticated() const;
539+ QString authenticationUser() const;
540+ bool promptless() const;
541+
542+public Q_SLOTS:
543+ void authenticate(const QString &username=QString());
544+ void respond(const QString &response);
545+ bool startSessionSync(const QString &session=QString());
546+
547+Q_SIGNALS:
548+ void showMessage(QString text, bool isError);
549+ void showPrompt(QString text, bool isSecret);
550+ void authenticationComplete();
551+ void promptlessChanged();
552+
553+private:
554+ GreeterPrivate * const d_ptr;
555+
556+ Q_DECLARE_PRIVATE(Greeter)
557+
558+private Q_SLOTS:
559+ void showMessageFilter(QString text, QLightDM::Greeter::MessageType type);
560+ void showPromptFilter(QString text, QLightDM::Greeter::PromptType type);
561+ void authenticationCompleteFilter();
562+};
563+
564+#endif
565
566=== added file 'plugins/LightDM/lightdm-greeter.cpp'
567--- plugins/LightDM/lightdm-greeter.cpp 1970-01-01 00:00:00 +0000
568+++ plugins/LightDM/lightdm-greeter.cpp 2013-05-13 15:26:27 +0000
569@@ -0,0 +1,195 @@
570+/*
571+ * Copyright (C) 2013 Canonical, Ltd.
572+ *
573+ * This program is free software; you can redistribute it and/or modify
574+ * it under the terms of the GNU General Public License as published by
575+ * the Free Software Foundation; version 3.
576+ *
577+ * This program is distributed in the hope that it will be useful,
578+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
579+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
580+ * GNU General Public License for more details.
581+ *
582+ * You should have received a copy of the GNU General Public License
583+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
584+ *
585+ * Author: Michael Terry <michael.terry@canonical.com>
586+ */
587+
588+#include "lightdm-greeter.h"
589+#include <QtCore/QCoreApplication>
590+
591+namespace QLightDM
592+{
593+
594+class GreeterPrivate
595+{
596+public:
597+ explicit GreeterPrivate(Greeter *parent);
598+
599+ bool authenticated;
600+ QString authenticationUser;
601+
602+protected:
603+ Greeter * const q_ptr;
604+
605+private:
606+ Q_DECLARE_PUBLIC(Greeter)
607+};
608+
609+GreeterPrivate::GreeterPrivate(Greeter* parent)
610+ : authenticated(false),
611+ authenticationUser(),
612+ q_ptr(parent)
613+{
614+}
615+
616+Greeter::Greeter(QObject *parent)
617+ : QObject(parent),
618+ d_ptr(new GreeterPrivate(this))
619+{
620+}
621+
622+Greeter::~Greeter()
623+{
624+}
625+
626+QString Greeter::authenticationUser() const
627+{
628+ Q_D(const Greeter);
629+ return d->authenticationUser;
630+}
631+
632+bool Greeter::hasGuestAccountHint() const
633+{
634+ return true;
635+}
636+
637+QString Greeter::getHint(const QString &name) const
638+{
639+ Q_UNUSED(name)
640+ return "";
641+}
642+
643+QString Greeter::defaultSessionHint() const
644+{
645+ return "ubuntu";
646+}
647+
648+bool Greeter::hideUsersHint() const
649+{
650+ return false;
651+}
652+
653+bool Greeter::showManualLoginHint() const
654+{
655+ return true;
656+}
657+
658+bool Greeter::showRemoteLoginHint() const
659+{
660+ return true;
661+}
662+
663+bool Greeter::lockHint () const
664+{
665+ return false;
666+}
667+
668+QString Greeter::selectUserHint() const
669+{
670+ return "";
671+}
672+
673+bool Greeter::selectGuestHint() const
674+{
675+ return false;
676+}
677+
678+QString Greeter::autologinUserHint() const
679+{
680+ return "";
681+}
682+
683+bool Greeter::autologinGuestHint() const
684+{
685+ return false;
686+}
687+
688+int Greeter::autologinTimeoutHint() const
689+{
690+ return 0;
691+}
692+
693+bool Greeter::inAuthentication() const
694+{
695+ return false;
696+}
697+
698+QString Greeter::hostname() const
699+{
700+ return "hostname1";
701+}
702+
703+bool Greeter::isAuthenticated() const
704+{
705+ Q_D(const Greeter);
706+ return d->authenticated;
707+}
708+
709+bool Greeter::connectSync()
710+{
711+ return true;
712+}
713+
714+void Greeter::authenticate(const QString &username)
715+{
716+ Q_D(Greeter);
717+
718+ d->authenticated = false;
719+ d->authenticationUser = username;
720+
721+ if (d->authenticationUser == "no-password") {
722+ d->authenticated = true;
723+ Q_EMIT authenticationComplete();
724+ } else {
725+ Q_EMIT showPrompt("Password: ", PromptTypeSecret);
726+ }
727+}
728+
729+void Greeter::authenticateAsGuest()
730+{}
731+
732+void Greeter::authenticateAutologin()
733+{}
734+
735+void Greeter::authenticateRemote(const QString &session, const QString &username)
736+{
737+ Q_UNUSED(session)
738+ Q_UNUSED(username)
739+}
740+
741+void Greeter::cancelAuthentication()
742+{}
743+
744+void Greeter::setLanguage (const QString &language)
745+{
746+ Q_UNUSED(language)
747+}
748+
749+bool Greeter::startSessionSync(const QString &session)
750+{
751+ Q_UNUSED(session)
752+ QCoreApplication::quit();
753+ return true;
754+}
755+
756+void Greeter::respond(const QString &response)
757+{
758+ Q_D(Greeter);
759+
760+ d->authenticated = (response == "password");
761+ Q_EMIT authenticationComplete();
762+}
763+
764+}
765
766=== added file 'plugins/LightDM/lightdm-greeter.h'
767--- plugins/LightDM/lightdm-greeter.h 1970-01-01 00:00:00 +0000
768+++ plugins/LightDM/lightdm-greeter.h 2013-05-13 15:26:27 +0000
769@@ -0,0 +1,105 @@
770+/*
771+ * Copyright (C) 2013 Canonical, Ltd.
772+ * Copyright (C) 2010-2011 David Edmundson.
773+ * Copyright (C) 2010-2011 Robert Ancell
774+ *
775+ * This program is free software; you can redistribute it and/or modify
776+ * it under the terms of the GNU General Public License as published by
777+ * the Free Software Foundation; version 3.
778+ *
779+ * This program is distributed in the hope that it will be useful,
780+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
781+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
782+ * GNU General Public License for more details.
783+ *
784+ * You should have received a copy of the GNU General Public License
785+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
786+ *
787+ * Author: David Edmundson <kde@davidedmundson.co.uk>
788+ */
789+
790+#ifndef LIGHTDM_GREETER_H
791+#define LIGHTDM_GREETER_H
792+
793+#include <QtCore/QObject>
794+#include <QtCore/QVariant>
795+
796+
797+namespace QLightDM
798+{
799+ class GreeterPrivate;
800+
801+class Q_DECL_EXPORT Greeter : public QObject
802+{
803+ Q_OBJECT
804+
805+ Q_PROPERTY(bool authenticated READ isAuthenticated ) //NOTFIY authenticationComplete
806+ Q_PROPERTY(QString authenticationUser READ authenticationUser )
807+ Q_PROPERTY(QString defaultSession READ defaultSessionHint CONSTANT)
808+ Q_PROPERTY(QString selectUser READ selectUserHint CONSTANT)
809+ Q_PROPERTY(bool selectGuest READ selectGuestHint CONSTANT)
810+
811+ Q_PROPERTY(QString hostname READ hostname CONSTANT)
812+ Q_PROPERTY(bool hasGuestAccount READ hasGuestAccountHint CONSTANT)
813+ Q_PROPERTY(bool locked READ lockHint CONSTANT)
814+
815+ Q_PROPERTY(QString hostname READ hostname CONSTANT)
816+
817+ Q_ENUMS(PromptType MessageType)
818+
819+public:
820+ enum PromptType {
821+ PromptTypeQuestion,
822+ PromptTypeSecret
823+ };
824+
825+ enum MessageType {
826+ MessageTypeInfo,
827+ MessageTypeError
828+ };
829+
830+ explicit Greeter(QObject* parent=0);
831+ virtual ~Greeter();
832+
833+ QString getHint(const QString &name) const;
834+ QString defaultSessionHint() const;
835+ bool hideUsersHint() const;
836+ bool showManualLoginHint() const;
837+ bool showRemoteLoginHint() const;
838+ bool lockHint () const;
839+ bool hasGuestAccountHint() const;
840+ QString selectUserHint() const;
841+ bool selectGuestHint() const;
842+ QString autologinUserHint() const;
843+ bool autologinGuestHint() const;
844+ int autologinTimeoutHint() const;
845+
846+ bool inAuthentication() const;
847+ bool isAuthenticated() const;
848+ QString authenticationUser() const;
849+ QString hostname() const;
850+
851+public Q_SLOTS:
852+ bool connectSync();
853+ void authenticate(const QString &username=QString());
854+ void authenticateAsGuest();
855+ void authenticateAutologin();
856+ void authenticateRemote(const QString &session=QString(), const QString &username=QString());
857+ void respond(const QString &response);
858+ void cancelAuthentication();
859+ void setLanguage (const QString &language);
860+ bool startSessionSync(const QString &session=QString());
861+
862+Q_SIGNALS:
863+ void showMessage(QString text, QLightDM::Greeter::MessageType type);
864+ void showPrompt(QString text, QLightDM::Greeter::PromptType type);
865+ void authenticationComplete();
866+ void autologinTimerExpired();
867+
868+private:
869+ GreeterPrivate *d_ptr;
870+ Q_DECLARE_PRIVATE(Greeter)
871+};
872+}
873+
874+#endif // LIGHTDM_GREETER_H
875
876=== added file 'plugins/LightDM/lightdm-usersmodel.cpp'
877--- plugins/LightDM/lightdm-usersmodel.cpp 1970-01-01 00:00:00 +0000
878+++ plugins/LightDM/lightdm-usersmodel.cpp 2013-05-13 15:26:27 +0000
879@@ -0,0 +1,178 @@
880+/*
881+ * Copyright (C) 2013 Canonical, Ltd.
882+ *
883+ * This program is free software; you can redistribute it and/or modify
884+ * it under the terms of the GNU General Public License as published by
885+ * the Free Software Foundation; version 3.
886+ *
887+ * This program is distributed in the hope that it will be useful,
888+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
889+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
890+ * GNU General Public License for more details.
891+ *
892+ * You should have received a copy of the GNU General Public License
893+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
894+ *
895+ * Author: Michael Terry <michael.terry@canonical.com>
896+ */
897+
898+// LightDM currently is Qt4 compatible, and so doesn't define setRoleNames.
899+// To use the same method of setting role name that it does, we
900+// set our compatibility to Qt4 here too.
901+#define QT_DISABLE_DEPRECATED_BEFORE QT_VERSION_CHECK(4, 0, 0)
902+
903+#include "lightdm-usersmodel.h"
904+#include <QtCore/QDebug>
905+#include <QtCore/QDir>
906+#include <QtCore/QString>
907+#include <QtGui/QIcon>
908+
909+using namespace QLightDM;
910+
911+class Entry
912+{
913+public:
914+ QString username;
915+ QString real_name;
916+ QString background;
917+ QString layouts;
918+ bool is_active;
919+ bool has_messages;
920+ QString session;
921+ QString infographic;
922+};
923+
924+const Entry fake_entries[] =
925+{
926+ { "anna", "Anna Olson", "*", 0, false, false, 0, 0 },
927+ { "lois", "Lois Mcqueen", "*", 0, false, false, 0, 0 },
928+ { "lola", "Lola Chang", "*", 0, false, false, 0, 0 },
929+ { "toomas", "Toomas Vilms", "*", 0, false, false, 0, 0 },
930+ { "empty-name", "", "*", 0, false, false, 0, 0 },
931+ { "no-password", "Guest", "*", 0, false, false, 0, 0 },
932+ { "", "", 0, 0, false, false, 0, 0 }
933+};
934+
935+namespace QLightDM
936+{
937+class UsersModelPrivate
938+{
939+public:
940+ explicit UsersModelPrivate(UsersModel *parent);
941+ QList<Entry> entries;
942+
943+protected:
944+ UsersModel * const q_ptr;
945+ void fillEntries();
946+
947+private:
948+ Q_DECLARE_PUBLIC(UsersModel)
949+};
950+}
951+
952+UsersModelPrivate::UsersModelPrivate(UsersModel* parent)
953+ : q_ptr(parent)
954+{
955+}
956+
957+void UsersModelPrivate::fillEntries()
958+{
959+ QDir bgdir = QDir("/usr/share/backgrounds/");
960+ QStringList backgrounds = bgdir.entryList(QDir::Files | QDir::NoDotAndDotDot);
961+ QDir infodir = QDir("/usr/share/demo-assets/shell/infographics/");
962+ QStringList infographics = infodir.entryList(QDir::Files | QDir::NoDotAndDotDot);
963+
964+ for (int i = 0, j = 0, k = 0; fake_entries[i].username != ""; i++) {
965+ Entry entry = fake_entries[i];
966+ if (entry.background == "*") {
967+ if (backgrounds.isEmpty()) {
968+ entry.background = ""; // to avoid warnings about a 404 on "*"
969+ } else {
970+ entry.background = bgdir.filePath(backgrounds[j++]);
971+ if (j >= backgrounds.length()) {
972+ j = 0;
973+ }
974+ }
975+ }
976+ if (!infographics.isEmpty()) {
977+ entry.infographic = infodir.filePath(infographics[k++]);
978+ if (k >= infographics.length()) {
979+ k = 0;
980+ }
981+ }
982+ entries.append(entry);
983+ }
984+}
985+
986+UsersModel::UsersModel(QObject *parent) :
987+ QAbstractListModel(parent),
988+ d_ptr(new UsersModelPrivate(this))
989+{
990+ Q_D(UsersModel);
991+ // Extend roleNames (we want to keep the "display" role)
992+ QHash<int, QByteArray> roles = roleNames();
993+ roles[NameRole] = "name";
994+ roles[RealNameRole] = "realName";
995+ roles[LoggedInRole] = "loggedIn";
996+ roles[BackgroundRole] = "background";
997+ roles[BackgroundPathRole] = "backgroundPath";
998+ roles[SessionRole] = "session";
999+ roles[HasMessagesRole] = "hasMessages";
1000+ roles[ImagePathRole] = "imagePath";
1001+ roles[InfographicRole] = "infographic";
1002+ setRoleNames(roles);
1003+ d->fillEntries();
1004+}
1005+
1006+UsersModel::~UsersModel()
1007+{
1008+ delete d_ptr;
1009+}
1010+
1011+int UsersModel::rowCount(const QModelIndex &parent) const
1012+{
1013+ Q_D(const UsersModel);
1014+
1015+ if (parent.isValid()) {
1016+ return 0;
1017+ } else { // parent is root
1018+ return d->entries.size();
1019+ }
1020+}
1021+
1022+QVariant UsersModel::data(const QModelIndex &index, int role) const
1023+{
1024+ Q_D(const UsersModel);
1025+
1026+ if (!index.isValid()) {
1027+ return QVariant();
1028+ }
1029+
1030+ int row = index.row();
1031+ switch (role) {
1032+ case Qt::DisplayRole:
1033+ return d->entries[row].real_name;
1034+ case Qt::DecorationRole:
1035+ return QIcon();
1036+ case UsersModel::NameRole:
1037+ return d->entries[row].username;
1038+ case UsersModel::RealNameRole:
1039+ return d->entries[row].real_name;
1040+ case UsersModel::SessionRole:
1041+ return d->entries[row].session;
1042+ case UsersModel::LoggedInRole:
1043+ return d->entries[row].is_active;
1044+ case UsersModel::BackgroundRole:
1045+ return QPixmap(d->entries[row].background);
1046+ case UsersModel::BackgroundPathRole:
1047+ return d->entries[row].background;
1048+ case UsersModel::HasMessagesRole:
1049+ return d->entries[row].has_messages;
1050+ case UsersModel::ImagePathRole:
1051+ return "";
1052+ case UsersModel::InfographicRole:
1053+ return d->entries[row].infographic;
1054+ default:
1055+ return QVariant();
1056+ }
1057+}
1058
1059=== added file 'plugins/LightDM/lightdm-usersmodel.h'
1060--- plugins/LightDM/lightdm-usersmodel.h 1970-01-01 00:00:00 +0000
1061+++ plugins/LightDM/lightdm-usersmodel.h 2013-05-13 15:26:27 +0000
1062@@ -0,0 +1,67 @@
1063+/*
1064+ * Copyright (C) 2013 Canonical, Ltd.
1065+ * Copyright (C) 2010-2011 David Edmundson.
1066+ *
1067+ * This program is free software; you can redistribute it and/or modify
1068+ * it under the terms of the GNU General Public License as published by
1069+ * the Free Software Foundation; version 3.
1070+ *
1071+ * This program is distributed in the hope that it will be useful,
1072+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1073+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1074+ * GNU General Public License for more details.
1075+ *
1076+ * You should have received a copy of the GNU General Public License
1077+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1078+ *
1079+ * Author: David Edmundson <kde@davidedmundson.co.uk>
1080+ */
1081+
1082+#ifndef LIGHTDM_USERSMODEL_H
1083+#define LIGHTDM_USERSMODEL_H
1084+
1085+#include <QtCore/QString>
1086+#include <QtCore/QSharedDataPointer>
1087+#include <QAbstractListModel>
1088+
1089+
1090+namespace QLightDM
1091+{
1092+class UsersModelPrivate;
1093+
1094+class Q_DECL_EXPORT UsersModel : public QAbstractListModel
1095+{
1096+ Q_OBJECT
1097+
1098+ Q_ENUMS(UserModelRoles)
1099+
1100+public:
1101+ explicit UsersModel(QObject *parent = 0);
1102+ ~UsersModel();
1103+
1104+ enum UserModelRoles {NameRole = Qt::UserRole,
1105+ RealNameRole,
1106+ LoggedInRole,
1107+ BackgroundRole,
1108+ SessionRole,
1109+ HasMessagesRole,
1110+ ImagePathRole,
1111+ BackgroundPathRole,
1112+ InfographicRole // WARNING: not a real lightdm role
1113+ };
1114+
1115+ int rowCount(const QModelIndex &parent) const;
1116+ QVariant data(const QModelIndex &index, int role) const;
1117+
1118+protected:
1119+
1120+private:
1121+ UsersModelPrivate * const d_ptr;
1122+
1123+ Q_DECLARE_PRIVATE(UsersModel)
1124+
1125+};
1126+
1127+}
1128+
1129+#endif // LIGHTDM_USERSMODEL_H
1130
1131=== added file 'plugins/LightDM/plugin.cpp'
1132--- plugins/LightDM/plugin.cpp 1970-01-01 00:00:00 +0000
1133+++ plugins/LightDM/plugin.cpp 2013-05-13 15:26:27 +0000
1134@@ -0,0 +1,47 @@
1135+/*
1136+ * Copyright (C) 2012,2013 Canonical, Ltd.
1137+ *
1138+ * This program is free software; you can redistribute it and/or modify
1139+ * it under the terms of the GNU General Public License as published by
1140+ * the Free Software Foundation; version 3.
1141+ *
1142+ * This program is distributed in the hope that it will be useful,
1143+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1144+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1145+ * GNU General Public License for more details.
1146+ *
1147+ * You should have received a copy of the GNU General Public License
1148+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1149+ *
1150+ * Authors: Gerry Boland <gerry.boland@canonical.com>
1151+ * Michael Terry <michael.terry@canonical.com>
1152+ */
1153+
1154+#include "plugin.h"
1155+#include "greeter.h"
1156+#include "lightdm-usersmodel.h"
1157+#include "usersmodel.h"
1158+#include <QtCore/QAbstractItemModel>
1159+#include <QtQml>
1160+
1161+static QObject *greeter_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
1162+{
1163+ Q_UNUSED(engine)
1164+ Q_UNUSED(scriptEngine)
1165+ return new Greeter();
1166+}
1167+
1168+static QObject *users_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
1169+{
1170+ Q_UNUSED(engine)
1171+ Q_UNUSED(scriptEngine)
1172+ return new UsersModel();
1173+}
1174+
1175+void LightDMPlugin::registerTypes(const char *uri)
1176+{
1177+ Q_ASSERT(uri == QLatin1String("LightDM"));
1178+ qmlRegisterSingletonType<Greeter>(uri, 0, 1, "Greeter", greeter_provider);
1179+ qmlRegisterSingletonType<UsersModel>(uri, 0, 1, "Users", users_provider);
1180+ qmlRegisterUncreatableType<QLightDM::UsersModel>(uri, 0, 1, "UserRoles", "Type is not instantiable");
1181+}
1182
1183=== added file 'plugins/LightDM/plugin.h'
1184--- plugins/LightDM/plugin.h 1970-01-01 00:00:00 +0000
1185+++ plugins/LightDM/plugin.h 2013-05-13 15:26:27 +0000
1186@@ -0,0 +1,35 @@
1187+/*
1188+ * Copyright (C) 2012,2013 Canonical, Ltd.
1189+ *
1190+ * This program is free software; you can redistribute it and/or modify
1191+ * it under the terms of the GNU General Public License as published by
1192+ * the Free Software Foundation; version 3.
1193+ *
1194+ * This program is distributed in the hope that it will be useful,
1195+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1196+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1197+ * GNU General Public License for more details.
1198+ *
1199+ * You should have received a copy of the GNU General Public License
1200+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1201+ *
1202+ * Authors: Gerry Boland <gerry.boland@canonical.com>
1203+ * Michael Terry <michael.terry@canonical.com>
1204+ */
1205+
1206+#ifndef LIGHTDM_PLUGIN_H
1207+#define LIGHTDM_PLUGIN_H
1208+
1209+#include <QtQml/QQmlEngine>
1210+#include <QtQml/QQmlExtensionPlugin>
1211+
1212+class LightDMPlugin : public QQmlExtensionPlugin
1213+{
1214+ Q_OBJECT
1215+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
1216+
1217+public:
1218+ void registerTypes(const char *uri);
1219+};
1220+
1221+#endif
1222
1223=== added file 'plugins/LightDM/qmldir'
1224--- plugins/LightDM/qmldir 1970-01-01 00:00:00 +0000
1225+++ plugins/LightDM/qmldir 2013-05-13 15:26:27 +0000
1226@@ -0,0 +1,2 @@
1227+module LightDM
1228+plugin LightDM-qml
1229
1230=== added file 'plugins/LightDM/usersmodel.cpp'
1231--- plugins/LightDM/usersmodel.cpp 1970-01-01 00:00:00 +0000
1232+++ plugins/LightDM/usersmodel.cpp 2013-05-13 15:26:27 +0000
1233@@ -0,0 +1,68 @@
1234+/*
1235+ * Copyright (C) 2013 Canonical, Ltd.
1236+ *
1237+ * This program is free software; you can redistribute it and/or modify
1238+ * it under the terms of the GNU General Public License as published by
1239+ * the Free Software Foundation; version 3.
1240+ *
1241+ * This program is distributed in the hope that it will be useful,
1242+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1243+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1244+ * GNU General Public License for more details.
1245+ *
1246+ * You should have received a copy of the GNU General Public License
1247+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1248+ *
1249+ * Author: Michael Terry <michael.terry@canonical.com>
1250+ */
1251+
1252+#include "usersmodel.h"
1253+#include "lightdm-usersmodel.h"
1254+#include <QtCore/QSortFilterProxyModel>
1255+
1256+// First, we define an internal class that wraps LightDM's UsersModel. This
1257+// class will modify some of the data coming from LightDM. For example, we
1258+// modify any empty Real Names into just normal Names.
1259+// (We can't modify the data directly in UsersModel below because it won't sort
1260+// using the modified data.)
1261+class MangleModel : public QSortFilterProxyModel
1262+{
1263+ Q_OBJECT
1264+
1265+public:
1266+ explicit MangleModel(QObject* parent=0);
1267+
1268+ virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
1269+};
1270+
1271+MangleModel::MangleModel(QObject* parent)
1272+ : QSortFilterProxyModel(parent)
1273+{
1274+ setSourceModel(new QLightDM::UsersModel(this));
1275+}
1276+
1277+QVariant MangleModel::data(const QModelIndex &index, int role) const
1278+{
1279+ QVariant data = QSortFilterProxyModel::data(index, role);
1280+
1281+ // If user's real name is empty, switch to unix name
1282+ if (role == QLightDM::UsersModel::RealNameRole && data.toString().isEmpty()) {
1283+ data = QSortFilterProxyModel::data(index, QLightDM::UsersModel::NameRole);
1284+ }
1285+
1286+ return data;
1287+}
1288+
1289+// **** Now we continue with actual UsersModel class ****
1290+
1291+UsersModel::UsersModel(QObject* parent)
1292+ : QSortFilterProxyModelQML(parent)
1293+{
1294+ setModel(new MangleModel(this));
1295+ setSortCaseSensitivity(Qt::CaseInsensitive);
1296+ setSortLocaleAware(true);
1297+ setSortRole(QLightDM::UsersModel::RealNameRole);
1298+ sort(0);
1299+}
1300+
1301+#include "usersmodel.moc"
1302
1303=== added file 'plugins/LightDM/usersmodel.h'
1304--- plugins/LightDM/usersmodel.h 1970-01-01 00:00:00 +0000
1305+++ plugins/LightDM/usersmodel.h 2013-05-13 15:26:27 +0000
1306@@ -0,0 +1,37 @@
1307+/*
1308+ * Copyright (C) 2012,2013 Canonical, Ltd.
1309+ *
1310+ * This program is free software; you can redistribute it and/or modify
1311+ * it under the terms of the GNU General Public License as published by
1312+ * the Free Software Foundation; version 3.
1313+ *
1314+ * This program is distributed in the hope that it will be useful,
1315+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1316+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1317+ * GNU General Public License for more details.
1318+ *
1319+ * You should have received a copy of the GNU General Public License
1320+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1321+ *
1322+ * Authors: Michael Terry <michael.terry@canonical.com>
1323+ */
1324+
1325+/* This class is a really tiny filter around QLightDM::UsersModel. There are
1326+ some operations that we want to edit a bit for the benefit of Qml.
1327+ Specifically, we want to sort users according to realName. */
1328+
1329+#ifndef USERSMODEL_H
1330+#define USERSMODEL_H
1331+
1332+#include "plugins/Utils/qsortfilterproxymodelqml.h"
1333+#include <QtCore/QObject>
1334+
1335+class UsersModel : public QSortFilterProxyModelQML
1336+{
1337+ Q_OBJECT
1338+
1339+public:
1340+ explicit UsersModel(QObject* parent=0);
1341+};
1342+
1343+#endif
1344
1345=== modified file 'plugins/Utils/qsortfilterproxymodelqml.cpp'
1346--- plugins/Utils/qsortfilterproxymodelqml.cpp 2013-05-09 14:23:10 +0000
1347+++ plugins/Utils/qsortfilterproxymodelqml.cpp 2013-05-13 15:26:27 +0000
1348@@ -57,6 +57,16 @@
1349 }
1350 }
1351
1352+QVariant
1353+QSortFilterProxyModelQML::data(int row, int role)
1354+{
1355+ if (sourceModel() == NULL) {
1356+ return QVariant();
1357+ }
1358+
1359+ return index(row, 0).data(role);
1360+}
1361+
1362 int
1363 QSortFilterProxyModelQML::totalCount() const
1364 {
1365
1366=== modified file 'plugins/Utils/qsortfilterproxymodelqml.h'
1367--- plugins/Utils/qsortfilterproxymodelqml.h 2013-05-08 11:22:15 +0000
1368+++ plugins/Utils/qsortfilterproxymodelqml.h 2013-05-13 15:26:27 +0000
1369@@ -31,6 +31,7 @@
1370 public:
1371 explicit QSortFilterProxyModelQML(QObject *parent = 0);
1372
1373+ Q_INVOKABLE QVariant data(int row, int role);
1374 Q_INVOKABLE int count();
1375 Q_INVOKABLE int findFirst(int role, const QVariant& value) const;
1376 virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const;
1377
1378=== modified file 'tests/autopilot/qml_phone_shell/tests/helpers.py'
1379--- tests/autopilot/qml_phone_shell/tests/helpers.py 2013-04-05 11:03:11 +0000
1380+++ tests/autopilot/qml_phone_shell/tests/helpers.py 2013-05-13 15:26:27 +0000
1381@@ -15,26 +15,49 @@
1382 class TestShellHelpers(object):
1383 """Helpers for testing the Shell"""
1384
1385- def unlock_greeter(self, retries=2):
1386+ def select_greeter_user(self, username):
1387 greeter = self.main_window.get_greeter()
1388 self.assertThat(greeter.created, Eventually(Equals(True)))
1389-
1390- if greeter.multiUser:
1391- login_loader = self.main_window.get_login_loader()
1392- self.assertThat(login_loader.progress, Eventually(Equals(1)))
1393- login_list = self.main_window.get_login_list()
1394- path_view = login_list.get_children_by_type("QQuickPathView")[0]
1395+ self.assertThat(greeter.multiUser, Eventually(Equals(True)))
1396+
1397+ login_loader = self.main_window.get_login_loader()
1398+ self.assertThat(login_loader.progress, Eventually(Equals(1)))
1399+
1400+ login_list = self.main_window.get_login_list()
1401+ path_view = login_list.get_children_by_type("QQuickPathView")[0]
1402+
1403+ try_count = 0
1404+ max_tries = 50 # just in case we go off rails
1405+ while try_count < max_tries:
1406 users = path_view.get_children_by_type("QQuickItem")
1407+ target_user = None
1408 for user in users:
1409 try:
1410 user_label = user.get_children_by_type("Label")[0]
1411- if user_label.text == "Guest":
1412- self.pointing_device.move_to_object(user_label)
1413- self.pointing_device.click()
1414- except:
1415+ if user_label.text == username:
1416+ target_user = user
1417+ break
1418+ elif target_user is None or user.y > target_user.y:
1419+ target_user = user
1420+ except Exception:
1421 pass
1422-
1423- password_field = login_list.get_children_by_type("TextField")[0]
1424+ if target_user is None:
1425+ break
1426+ user_label = target_user.get_children_by_type("Label")[0]
1427+ self.pointing_device.move_to_object(user_label)
1428+ self.pointing_device.click()
1429+ self.assertThat(path_view.movingInternally, Eventually(Equals(False)))
1430+ if user_label.text == username:
1431+ return login_list.get_children_by_type("TextField")[0]
1432+ try_count = try_count + 1
1433+ self.fail() # We didn't find it
1434+
1435+ def unlock_greeter(self, retries=2):
1436+ greeter = self.main_window.get_greeter()
1437+ self.assertThat(greeter.created, Eventually(Equals(True)))
1438+
1439+ if greeter.multiUser:
1440+ password_field = self.select_greeter_user("Guest")
1441 self.pointing_device.move_to_object(password_field)
1442 self.assertThat(password_field.opacity, Eventually(Equals(1)))
1443 self.pointing_device.click()
1444
1445=== modified file 'tests/plugins/Utils/qsortfilterproxymodeltest.cpp'
1446--- tests/plugins/Utils/qsortfilterproxymodeltest.cpp 2013-05-08 09:51:55 +0000
1447+++ tests/plugins/Utils/qsortfilterproxymodeltest.cpp 2013-05-13 15:26:27 +0000
1448@@ -263,6 +263,26 @@
1449 proxy.setModel(&model);
1450 QCOMPARE(spyOnModelChanged.count(), 3);
1451 }
1452+
1453+ void testData() {
1454+ QSortFilterProxyModelQML proxy;
1455+ MockListModel model, model2;
1456+
1457+ QStringList rows;
1458+ rows << "a" << "c" << "b";
1459+ model.appendRows(rows);
1460+
1461+ proxy.setModel(&model);
1462+ proxy.sort(0);
1463+
1464+ QCOMPARE(proxy.data(-1, Qt::DisplayRole), QVariant());
1465+ QCOMPARE(proxy.data(3, Qt::DisplayRole), QVariant());
1466+ QCOMPARE(proxy.data(0, Qt::DisplayRole - 1), QVariant());
1467+ QCOMPARE(proxy.data(0, Qt::DisplayRole + 1), QVariant());
1468+ QCOMPARE(proxy.data(0, Qt::DisplayRole), QVariant("a"));
1469+ QCOMPARE(proxy.data(1, Qt::DisplayRole), QVariant("b"));
1470+ QCOMPARE(proxy.data(2, Qt::DisplayRole), QVariant("c"));
1471+ }
1472 };
1473
1474 QTEST_MAIN(QSortFilterProxyModelTest)
1475
1476=== modified file 'tests/qmltests/CMakeLists.txt'
1477--- tests/qmltests/CMakeLists.txt 2013-05-09 07:01:25 +0000
1478+++ tests/qmltests/CMakeLists.txt 2013-05-13 15:26:27 +0000
1479@@ -49,7 +49,7 @@
1480 ${CMAKE_BINARY_DIR}/tests/mocks)
1481 add_qml_test(Dash/Apps RunningApplicationsGrid IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks)
1482 add_qml_test(Dash/People PeopleFilterGrid IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${CMAKE_CURRENT_SOURCE_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
1483-add_qml_test(Greeter Greeter)
1484+add_qml_test(Greeter Greeter IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/plugins)
1485 add_qml_test(Hud Hud)
1486 add_qml_test(Hud Result)
1487 add_qml_test(Launcher Launcher)
1488
1489=== modified file 'tests/qmltests/Greeter/tst_Greeter.qml'
1490--- tests/qmltests/Greeter/tst_Greeter.qml 2013-04-17 13:01:10 +0000
1491+++ tests/qmltests/Greeter/tst_Greeter.qml 2013-05-13 15:26:27 +0000
1492@@ -19,6 +19,7 @@
1493 import ".."
1494 import "../../../Greeter"
1495 import Ubuntu.Components 0.1
1496+import LightDM 0.1 as LightDM
1497 import Unity.Test 0.1 as UT
1498
1499 Item {
1500@@ -46,53 +47,69 @@
1501 name: "Greeter"
1502 when: windowShown
1503
1504+ function select_user(name) {
1505+ // We could be anywhere in list; find target index to know which direction
1506+ for (var i = 0; i < greeter.model.count; i++) {
1507+ if (greeter.model.data(i, LightDM.UserRoles.NameRole) == name) {
1508+ break
1509+ }
1510+ }
1511+ if (i == greeter.model.count) {
1512+ return
1513+ }
1514+ var pathview = findChild(greeter, "userList")
1515+ while (pathview.currentIndex != i) {
1516+ var next = pathview.currentIndex + 1
1517+ if (pathview.currentIndex > i) {
1518+ next = pathview.currentIndex - 1
1519+ }
1520+ var account = findChild(greeter, "username"+next)
1521+ mouseClick(account, 1, 1)
1522+ tryCompare(pathview, "currentIndex", next)
1523+ }
1524+ }
1525+
1526 function test_cycle_data() {
1527- tryCompare(findChild(greeter, "userList"), "count", 5)
1528+ tryCompare(findChild(greeter, "userList"), "count", 6)
1529 var data = new Array()
1530 for (var i = 1; i < greeter.model.count; i++) {
1531- data[i] = {tag: greeter.model.get(i).name, username: "username"+i, uid: i }
1532+ data[i] = {tag: greeter.model.data(i, LightDM.UserRoles.NameRole), username: "username"+i, uid: i }
1533 }
1534- data[greeter.model.count] = {tag: greeter.model.get(0).name, username: "username0", uid: 0 }
1535+ // We add first name last in data to guarantee a selected signal
1536+ data[greeter.model.count] = {tag: greeter.model.data(0, LightDM.UserRoles.NameRole), username: "username0", uid: 0 }
1537 return data
1538 }
1539
1540 function test_cycle(data) {
1541- tryCompare(findChild(greeter, "userList"), "count", 5)
1542+ tryCompare(findChild(greeter, "userList"), "count", 6)
1543 selectionSpy.clear();
1544 var user = findChild(greeter, data.username)
1545 mouseClick(user, user.width / 2, user.height / 2)
1546 var pathview = findChild(greeter, "userList")
1547 tryCompare(pathview, "currentIndex", data.uid)
1548- tryCompare(greeter, "locked", greeter.model.get(data.uid).password !== undefined)
1549+ tryCompare(greeter, "locked", data.tag !== "no-password")
1550 tryCompare(selectionSpy, "count", 1)
1551 }
1552
1553 function test_unlock_password() {
1554+ select_user("lois") // to guarantee a selected signal
1555 unlockSpy.clear()
1556- // First one is Lolas account right now. Replace with password protected user from lightdm
1557- var account = findChild(greeter, "username1")
1558- mouseClick(account, 1, 1)
1559- account = findChild(greeter, "username0")
1560- mouseClick(account, 1, 1)
1561+ select_user("lola")
1562 var passwordInput = findChild(greeter, "passwordInput")
1563- tryCompare(passwordInput, "opacity", 0)
1564 tryCompare(passwordInput, "opacity", 1)
1565 mouseClick(passwordInput, 1, 1)
1566 compare(unlockSpy.count, 0)
1567- typeString(greeter.model.get(0).password)
1568+ typeString("password")
1569 keyClick(Qt.Key_Enter)
1570 unlockSpy.wait()
1571 }
1572
1573 function test_unlock_wrong_password() {
1574+ select_user("lois") // to guarantee a selected signal
1575 unlockSpy.clear()
1576- // First one is Lolas account right now. Replace with password protected user from lightdm
1577- var account = findChild(greeter, "username1")
1578- mouseClick(account, 1, 1)
1579- account = findChild(greeter, "username0")
1580- var passwordInput = findChild(greeter, "passwordInput")
1581- mouseClick(account, 1, 1)
1582+ select_user("lola")
1583 wait(0) // spin event loop to start any pending animations
1584+ var passwordInput = findChild(greeter, "passwordInput")
1585 tryCompare(passwordInput, "opacity", 1) // wait for opacity animation to be finished
1586 mouseClick(passwordInput, 1, 1)
1587 compare(unlockSpy.count, 0)
1588@@ -103,14 +120,23 @@
1589
1590 function test_unlock_no_password() {
1591 unlockSpy.clear()
1592- // Last one is the guest account for now. Replace with passwordless user from lightdm
1593- var guestAccount = findChild(greeter, "username" + (greeter.model.count - 1))
1594- mouseClick(guestAccount, guestAccount.width / 2, guestAccount.height / 2)
1595+ select_user("no-password")
1596 var passwordInput = findChild(greeter, "passwordInput")
1597 tryCompare(passwordInput, "opacity", 1)
1598 mouseClick(passwordInput, 1, 1)
1599 unlockSpy.wait()
1600 compare(unlockSpy.count, 1)
1601 }
1602+
1603+ function test_empty_name() {
1604+ for (var i = 0; i < greeter.model.count; i++) {
1605+ if (greeter.model.data(i, LightDM.UserRoles.NameRole) == "empty-name") {
1606+ compare(greeter.model.data(i, LightDM.UserRoles.RealNameRole), greeter.model.data(i, LightDM.UserRoles.NameRole))
1607+ return
1608+ }
1609+ }
1610+ // Never found empty-name...
1611+ throw new Error("QtQuickTest::fail")
1612+ }
1613 }
1614 }

Subscribers

People subscribed via source and target branches