on xenial help sign in
merge silo 2272
e8b0d11Lukáš Tinkl
CMakeLists.txt+1−1
@@ -70,7 +70,7 @@ find_package(Qt5DBus 5.6 REQUIRED)
70find_package(Qt5Concurrent 5.6 REQUIRED)
70find_package(Qt5Concurrent 5.6 REQUIRED)
71find_package(Qt5Sql 5.6 REQUIRED)
71find_package(Qt5Sql 5.6 REQUIRED)
72
72
73−pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=23)
73+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=25)
74pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
74pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
75pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
75pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
76pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
76pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
cmake/modules/QmlTest.cmake+8−3
@@ -264,12 +264,16 @@ endfunction()
264# installed system.
264# installed system.
265
265
266function(add_meta_test TARGET_NAME)
266function(add_meta_test TARGET_NAME)
267− cmake_parse_arguments(TEST "" "" "DEPENDS" ${ARGN})
267+ cmake_parse_arguments(TEST "SERIAL" "" "DEPENDS" ${ARGN})
268
268
269 add_custom_target(${TARGET_NAME})
269 add_custom_target(${TARGET_NAME})
270
270
271 set(filename "${CMAKE_BINARY_DIR}/tests/scripts/${TARGET_NAME}.sh")
271 set(filename "${CMAKE_BINARY_DIR}/tests/scripts/${TARGET_NAME}.sh")
272+ if(TEST_SERIAL)
273+ file(WRITE "${filename}" "#!/bin/sh\n\n")
274+ else()
272− file(WRITE "${filename}" "#!/usr/bin/parallel --shebang --no-notice\n\n")
275+ file(WRITE "${filename}" "#!/usr/bin/parallel --shebang --no-notice\n\n")
276+ endif()
273
277
274 add_meta_dependencies(${TARGET_NAME} DEPENDS ${TEST_DEPENDS})
278 add_meta_dependencies(${TARGET_NAME} DEPENDS ${TEST_DEPENDS})
275 # else we will write the rest of the script as we add cmake targets
279 # else we will write the rest of the script as we add cmake targets
@@ -307,6 +311,7 @@ function(install_test_script TARGET_NAME)
307 foreach(ONE_CMD ${TEST_COMMAND})
311 foreach(ONE_CMD ${TEST_COMMAND})
308 set(script "${script}'${ONE_CMD}' ")
312 set(script "${script}'${ONE_CMD}' ")
309 endforeach()
313 endforeach()
314+ set(script "${script}\"\$@\"") # Allow passing arguments if desired
310
315
311 set(filename "${CMAKE_BINARY_DIR}/tests/scripts/${TARGET_NAME}.sh")
316 set(filename "${CMAKE_BINARY_DIR}/tests/scripts/${TARGET_NAME}.sh")
312
317
@@ -370,7 +375,7 @@ function(add_meta_dependencies UPSTREAM_TARGET)
370 # add depend to the meta test script that we will install on system
375 # add depend to the meta test script that we will install on system
371 set(filename "${CMAKE_BINARY_DIR}/tests/scripts/${UPSTREAM_TARGET}.sh")
376 set(filename "${CMAKE_BINARY_DIR}/tests/scripts/${UPSTREAM_TARGET}.sh")
372 if (EXISTS "${filename}")
377 if (EXISTS "${filename}")
373− file(APPEND "${filename}" "${CMAKE_INSTALL_PREFIX}/${SHELL_PRIVATE_LIBDIR}/tests/scripts/${depend}.sh\n")
378+ file(APPEND "${filename}" "${CMAKE_INSTALL_PREFIX}/${SHELL_PRIVATE_LIBDIR}/tests/scripts/${depend}.sh \"\$@\" 2>&1\n")
374 endif()
379 endif()
375 endforeach()
380 endforeach()
376endfunction()
381endfunction()
data/com.canonical.Unity8.gschema.xml+5
@@ -53,6 +53,11 @@
53 <summary>Enable or disable the indicator pull down menus</summary>
53 <summary>Enable or disable the indicator pull down menus</summary>
54 <description>Toggle the availability of the indicator pull down menus</description>
54 <description>Toggle the availability of the indicator pull down menus</description>
55 </key>
55 </key>
56+ <key type="s" name="appstore-uri">
57+ <default>'scope://com.canonical.scopes.clickstore'</default>
58+ <summary>The uri to the app store</summary>
59+ <description>This will be used whenever the user triggers an action to open the app store.</description>
60+ </key>
56 </schema>
61 </schema>
57
62
58 <schema path="/com/canonical/unity8/greeter/" id="com.canonical.Unity8.Greeter" gettext-domain="unity8">
63 <schema path="/com/canonical/unity8/greeter/" id="com.canonical.Unity8.Greeter" gettext-domain="unity8">
debian/changelog+51
@@ -1,3 +1,54 @@
1+unity8 (8.15+17.04.20170124-0ubuntu1) zesty; urgency=medium
2+
3+ [ Albert Astals Cid ]
4+ * Limit tab-focus travelling on dialogs with a fence
5+ * Restore focus to where it was when our ShellDialogs get unloaded
6+ * Update current session after changing the user
7+ * Add keyboard navigation for Indicators
8+ * a window -> the current window
9+ * There's no spreadDelegate_ anymore
10+
11+ [ Daniel d'Andrada ]
12+ * Simplify DecoratedWindow
13+ * Remove unnecessary warning message
14+
15+ [ Josh Arenson ]
16+ * Add a test for the session chooser icon in the greeter's sessions
17+ list
18+
19+ [ Lukáš Tinkl ]
20+ * Fix keymap not being applied on the shell itself (LP: #1626435)
21+ * Shell dialog improvements (kbd focus, mouse eater)
22+ * Start searching directly as you type, w/o having to first
23+ focus/click the search field.
24+ * Add a test for the real implementation of WindowStateStorage
25+ * Use a four finger gesture to open the drawer, much like in u7
26+
27+ [ Michael Terry ]
28+ * Simplify the lightdm mock to make future greeter improvements easier
29+ to test.
30+ * Add support for guest sessions in unity8-greeter.
31+ * Use a model for PAM prompts, supporting more possible interactions.
32+ * Fix grouping of autopkg output and allow optionally passing
33+ arguments to installed test scripts.
34+ * Add support for LightDM hints for manual logins and hiding normal
35+ users.
36+
37+ [ Michael Zanetti ]
38+ * hint the launcher to indicate a successful size change to the user
39+ (LP: #1646457)
40+ * Improvements for the appdrawer (LP: #1648173)
41+ * Adjust home key to still focus the dash instead of messing with the
42+ drawer
43+ * allow 4 finger simulation with mousetouchadaptor
44+
45+ [ Nick Dedekind ]
46+ * Added Alt+F10 shortcut to open app menus. (LP: #1656896)
47+ * Fixed menu layout width calculations. (LP: #1657050)
48+ * Skip Panel::test_drag_indicator_item_down_shows_menu
49+
50+ -- Michał Sawicz <michal.sawicz@canonical.com> Tue, 24 Jan 2017 07:46:58 +0000
51+
1unity8 (8.15+17.04.20170110.4-0ubuntu1) zesty; urgency=medium
52unity8 (8.15+17.04.20170110.4-0ubuntu1) zesty; urgency=medium
2
53
3 [ Albert Astals Cid ]
54 [ Albert Astals Cid ]
debian/control+1−1
@@ -38,7 +38,7 @@ Build-Depends: cmake,
38 libubuntugestures5-private-dev (>= 1.3.2030),
38 libubuntugestures5-private-dev (>= 1.3.2030),
39 libudev-dev,
39 libudev-dev,
40 libudm-common-dev,
40 libudm-common-dev,
41− libunity-api-dev (>= 8.0),
41+ libunity-api-dev (>= 8.1),
42 libusermetricsoutput1-dev,
42 libusermetricsoutput1-dev,
43# Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
43# Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
44 libx11-dev[!arm64 !armhf],
44 libx11-dev[!arm64 !armhf],
debian/rules+1−1
@@ -40,4 +40,4 @@ override_dh_makeshlibs:
40override_dh_shlibdeps:
40override_dh_shlibdeps:
41 # Some mock libraries link against liblightdm-qt5-3.so which we want to
41 # Some mock libraries link against liblightdm-qt5-3.so which we want to
42 # avoid, since we only really link against our mock one, not the system one.
42 # avoid, since we only really link against our mock one, not the system one.
43− dh_shlibdeps -XlibMockLightDM-qml.so -XlibMockAccountsService-qml.so -Lunity8-private
43+ dh_shlibdeps -XlibMockAccountsService-qml.so -Lunity8-private
plugins/LightDM/CMakeLists.txt+1−1
@@ -7,7 +7,6 @@ include_directories(
7 ${CMAKE_CURRENT_SOURCE_DIR}
7 ${CMAKE_CURRENT_SOURCE_DIR}
8 ${CMAKE_CURRENT_BINARY_DIR}
8 ${CMAKE_CURRENT_BINARY_DIR}
9 ${CMAKE_SOURCE_DIR}/plugins/Utils
9 ${CMAKE_SOURCE_DIR}/plugins/Utils
10− ${CMAKE_BINARY_DIR}/tests/mocks/LightDM/IntegratedLightDM
11 ${libunity8-private_SOURCE_DIR}
10 ${libunity8-private_SOURCE_DIR}
12 ${LIBUSERMETRICSOUTPUT_INCLUDE_DIRS}
11 ${LIBUSERMETRICSOUTPUT_INCLUDE_DIRS}
13)
12)
@@ -18,6 +17,7 @@ set(QMLPLUGIN_SRC
18 DBusGreeterList.cpp
17 DBusGreeterList.cpp
19 Greeter.cpp
18 Greeter.cpp
20 plugin.cpp
19 plugin.cpp
20+ PromptsModel.cpp
21 SessionsModel.cpp
21 SessionsModel.cpp
22 UsersModel.cpp
22 UsersModel.cpp
23 )
23 )
plugins/LightDM/DBusGreeterList.cpp+6−6
@@ -25,7 +25,7 @@ DBusGreeterList::DBusGreeterList(Greeter *greeter, const QString &path)
25 m_greeter(greeter)
25 m_greeter(greeter)
26{
26{
27 connect(m_greeter, &Greeter::authenticationUserChanged, this, &DBusGreeterList::authenticationUserChangedHandler);
27 connect(m_greeter, &Greeter::authenticationUserChanged, this, &DBusGreeterList::authenticationUserChangedHandler);
28− connect(m_greeter, &Greeter::promptlessChanged, this, &DBusGreeterList::promptlessChangedHandler);
28+ connect(m_greeter, &Greeter::isAuthenticatedChanged, this, &DBusGreeterList::authenticatedChangedHandler);
29}
29}
30
30
31QString DBusGreeterList::GetActiveEntry() const
31QString DBusGreeterList::GetActiveEntry() const
@@ -40,16 +40,16 @@ void DBusGreeterList::SetActiveEntry(const QString &entry)
40
40
41bool DBusGreeterList::entryIsLocked() const
41bool DBusGreeterList::entryIsLocked() const
42{
42{
43− return !m_greeter->promptless();
43+ return !m_greeter->isAuthenticated();
44}
44}
45
45
46−void DBusGreeterList::authenticationUserChangedHandler(const QString &user)
46+void DBusGreeterList::authenticationUserChangedHandler()
47{
47{
48− notifyPropertyChanged(QStringLiteral("ActiveEntry"), user);
48+ notifyPropertyChanged(QStringLiteral("ActiveEntry"), m_greeter->authenticationUser());
49− Q_EMIT EntrySelected(user);
49+ Q_EMIT EntrySelected(m_greeter->authenticationUser());
50}
50}
51
51
52−void DBusGreeterList::promptlessChangedHandler()
52+void DBusGreeterList::authenticatedChangedHandler()
53{
53{
54 notifyPropertyChanged(QStringLiteral("EntryIsLocked"), entryIsLocked());
54 notifyPropertyChanged(QStringLiteral("EntryIsLocked"), entryIsLocked());
55 Q_EMIT entryIsLockedChanged();
55 Q_EMIT entryIsLockedChanged();
plugins/LightDM/DBusGreeterList.h+2−2
@@ -47,8 +47,8 @@ Q_SIGNALS:
47 void entryIsLockedChanged();
47 void entryIsLockedChanged();
48
48
49private Q_SLOTS:
49private Q_SLOTS:
50− void authenticationUserChangedHandler(const QString &user);
50+ void authenticationUserChangedHandler();
51− void promptlessChangedHandler();
51+ void authenticatedChangedHandler();
52
52
53private:
53private:
54 Greeter *m_greeter;
54 Greeter *m_greeter;
plugins/LightDM/Greeter.cpp+137−23
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013, 2015 Canonical, Ltd.
2+ * Copyright (C) 2013-2017 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -17,13 +17,16 @@
17
17
18#include "Greeter.h"
18#include "Greeter.h"
19#include "GreeterPrivate.h"
19#include "GreeterPrivate.h"
20+#include <QCoreApplication>
20#include <libintl.h>
21#include <libintl.h>
21
22
23+static Greeter *singleton = nullptr;
24+
22GreeterPrivate::GreeterPrivate(Greeter* parent)
25GreeterPrivate::GreeterPrivate(Greeter* parent)
23 : m_greeter(new QLightDM::Greeter(parent)),
26 : m_greeter(new QLightDM::Greeter(parent)),
24 m_active(false),
27 m_active(false),
25− wasPrompted(false),
28+ responded(false),
26− promptless(false),
29+ everResponded(false),
27 q_ptr(parent)
30 q_ptr(parent)
28{
31{
29}
32}
@@ -41,9 +44,32 @@ Greeter::Greeter(QObject* parent)
41 connect(d->m_greeter, &QLightDM::Greeter::authenticationComplete,
44 connect(d->m_greeter, &QLightDM::Greeter::authenticationComplete,
42 this, &Greeter::authenticationCompleteFilter);
45 this, &Greeter::authenticationCompleteFilter);
43
46
47+ // Don't get stuck waiting for PAM as we shut down.
48+ connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit,
49+ d->m_greeter, &QLightDM::Greeter::cancelAuthentication);
50+
44 d->m_greeter->connectSync();
51 d->m_greeter->connectSync();
45}
52}
46
53
54+Greeter::~Greeter()
55+{
56+ singleton = nullptr;
57+}
58+
59+Greeter *Greeter::instance()
60+{
61+ if (!singleton) {
62+ singleton = new Greeter;
63+ }
64+ return singleton;
65+}
66+
67+PromptsModel *Greeter::promptsModel()
68+{
69+ Q_D(Greeter);
70+ return &d->prompts;
71+}
72+
47bool Greeter::isActive() const
73bool Greeter::isActive() const
48{
74{
49 Q_D(const Greeter);
75 Q_D(const Greeter);
@@ -68,7 +94,16 @@ bool Greeter::isAuthenticated() const
68QString Greeter::authenticationUser() const
94QString Greeter::authenticationUser() const
69{
95{
70 Q_D(const Greeter);
96 Q_D(const Greeter);
97+ return d->cachedAuthUser;
98+}
99+
100+void Greeter::checkAuthenticationUser()
101+{
102+ Q_D(Greeter);
103+ if (d->cachedAuthUser != d->m_greeter->authenticationUser()) {
71− return d->m_greeter->authenticationUser();
104+ d->cachedAuthUser = d->m_greeter->authenticationUser();
105+ Q_EMIT authenticationUserChanged();
106+ }
72}
107}
73
108
74QString Greeter::defaultSessionHint() const
109QString Greeter::defaultSessionHint() const
@@ -77,35 +112,64 @@ QString Greeter::defaultSessionHint() const
77 return d->m_greeter->defaultSessionHint();
112 return d->m_greeter->defaultSessionHint();
78}
113}
79
114
80−bool Greeter::promptless() const
115+QString Greeter::selectUser() const
81{
116{
82 Q_D(const Greeter);
117 Q_D(const Greeter);
83− return d->promptless;
118+ if (hasGuestAccount() && d->m_greeter->selectGuestHint()) {
119+ return QStringLiteral("*guest");
120+ } else {
121+ return d->m_greeter->selectUserHint();
122+ }
84}
123}
85
124
86−QString Greeter::selectUser() const
125+bool Greeter::hasGuestAccount() const
126+{
127+ Q_D(const Greeter);
128+ return d->m_greeter->hasGuestAccountHint();
129+}
130+
131+bool Greeter::showManualLoginHint() const
87{
132{
88 Q_D(const Greeter);
133 Q_D(const Greeter);
89− return d->m_greeter->selectUserHint();
134+ return d->m_greeter->showManualLoginHint();
135+}
136+
137+bool Greeter::hideUsersHint() const
138+{
139+ Q_D(const Greeter);
140+ return d->m_greeter->hideUsersHint();
90}
141}
91
142
92void Greeter::authenticate(const QString &username)
143void Greeter::authenticate(const QString &username)
93{
144{
94 Q_D(Greeter);
145 Q_D(Greeter);
95− d->wasPrompted = false;
146+ d->prompts.clear();
96− if (d->promptless) {
147+ d->responded = false;
97− d->promptless = false;
148+ d->everResponded = false;
98− Q_EMIT promptlessChanged();
149+
150+ if (authenticationUser() == username) {
151+ d->prompts = d->leftovers;
99 }
152 }
153+ d->leftovers.clear();
100
154
155+ if (username == QStringLiteral("*guest")) {
156+ d->m_greeter->authenticateAsGuest();
157+ } else if (username == QStringLiteral("*other")) {
101− d->m_greeter->authenticate(username);
158+ d->m_greeter->authenticate(nullptr);
159+ } else {
160+ d->m_greeter->authenticate(username);
161+ }
162+
163+ Q_EMIT authenticationStarted();
102 Q_EMIT isAuthenticatedChanged();
164 Q_EMIT isAuthenticatedChanged();
103− Q_EMIT authenticationUserChanged(username);
165+ checkAuthenticationUser();
104}
166}
105
167
106void Greeter::respond(const QString &response)
168void Greeter::respond(const QString &response)
107{
169{
108 Q_D(Greeter);
170 Q_D(Greeter);
171+ d->responded = true;
172+ d->everResponded = true;
109 d->m_greeter->respond(response);
173 d->m_greeter->respond(response);
110}
174}
111
175
@@ -118,32 +182,82 @@ bool Greeter::startSessionSync(const QString &session)
118void Greeter::showPromptFilter(const QString &text, QLightDM::Greeter::PromptType type)
182void Greeter::showPromptFilter(const QString &text, QLightDM::Greeter::PromptType type)
119{
183{
120 Q_D(Greeter);
184 Q_D(Greeter);
121− d->wasPrompted = true;
185+
186+ checkAuthenticationUser(); // may have changed in liblightdm
122
187
123 bool isDefaultPrompt = (text == dgettext("Linux-PAM", "Password: "));
188 bool isDefaultPrompt = (text == dgettext("Linux-PAM", "Password: "));
189+ bool isSecret = type == QLightDM::Greeter::PromptTypeSecret;
190+
191+ QString trimmedText;
192+ if (!isDefaultPrompt)
193+ trimmedText = text.trimmed();
124
194
125 // Strip prompt of any colons at the end
195 // Strip prompt of any colons at the end
126− QString trimmedText = text.trimmed();
127 if (trimmedText.endsWith(':') || trimmedText.endsWith(QStringLiteral(":"))) {
196 if (trimmedText.endsWith(':') || trimmedText.endsWith(QStringLiteral(":"))) {
128 trimmedText.chop(1);
197 trimmedText.chop(1);
129 }
198 }
130
199
131− Q_EMIT showPrompt(trimmedText, type == QLightDM::Greeter::PromptTypeSecret, isDefaultPrompt);
200+ if (trimmedText == "login") {
201+ // 'login' is provided untranslated by LightDM when asking for a manual
202+ // login username.
203+ trimmedText = gettext("Username");
204+ }
205+
206+ if (d->responded) {
207+ d->prompts.clear();
208+ d->responded = false;
209+ }
210+
211+ d->prompts.append(trimmedText, isSecret ? PromptsModel::Secret : PromptsModel::Question);
132}
212}
133
213
134void Greeter::showMessageFilter(const QString &text, QLightDM::Greeter::MessageType type)
214void Greeter::showMessageFilter(const QString &text, QLightDM::Greeter::MessageType type)
135{
215{
216+ Q_D(Greeter);
217+
218+ checkAuthenticationUser(); // may have changed in liblightdm
219+
136− Q_EMIT showMessage(text, type == QLightDM::Greeter::MessageTypeError);
220+ bool isError = type == QLightDM::Greeter::MessageTypeError;
221+
222+ if (d->responded) {
223+ d->prompts.clear();
224+ d->responded = false;
225+ }
226+ d->prompts.append(text, isError? PromptsModel::Error : PromptsModel::Message);
137}
227}
138
228
139void Greeter::authenticationCompleteFilter()
229void Greeter::authenticationCompleteFilter()
140{
230{
141 Q_D(Greeter);
231 Q_D(Greeter);
142− if (!d->wasPrompted) {
143− d->promptless = true;
144− Q_EMIT promptlessChanged();
145− }
146
232
147 Q_EMIT isAuthenticatedChanged();
233 Q_EMIT isAuthenticatedChanged();
148− Q_EMIT authenticationComplete();
234+
235+ bool automatic = !d->everResponded;
236+ bool pamHasLeftoverMessages = !d->prompts.hasPrompt() && d->prompts.rowCount() > 0;
237+
238+ if (!isAuthenticated()) {
239+ if (pamHasLeftoverMessages) {
240+ d->leftovers = d->prompts; // Prefer PAM's messages
241+ } else if (automatic) {
242+ d->leftovers.append(gettext("Failed to authenticate"), PromptsModel::Error);
243+ } else {
244+ d->leftovers.append(gettext("Invalid password, please try again"), PromptsModel::Error);
245+ }
246+ } else if (pamHasLeftoverMessages) {
247+ automatic = true; // treat this successful login as automatic, so user sees message
248+ d->leftovers = d->prompts;
249+ }
250+
251+ if (automatic) {
252+ d->prompts = d->leftovers; // OK, we'll just use these now
253+ d->leftovers.clear();
254+ d->prompts.append(isAuthenticated() ? gettext("Log In") : gettext("Retry"),
255+ PromptsModel::Button);
256+ }
257+
258+ if (isAuthenticated()) {
259+ Q_EMIT loginSuccess(automatic);
260+ } else {
261+ Q_EMIT loginError(automatic);
262+ }
149}
263}
plugins/LightDM/Greeter.h+17−13
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2012,2013,2015 Canonical, Ltd.
2+ * Copyright (C) 2012-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -21,13 +21,13 @@
21 such edits in the future, and by inserting ourselves here, we have more
21 such edits in the future, and by inserting ourselves here, we have more
22 control. */
22 control. */
23
23
24−#ifndef UNITY_GREETER_H
24+#pragma once
25−#define UNITY_GREETER_H
26
25
27#include <QLightDM/Greeter>
26#include <QLightDM/Greeter>
28#include <QtCore/QObject>
27#include <QtCore/QObject>
29
28
30class GreeterPrivate;
29class GreeterPrivate;
30+class PromptsModel;
31
31
32class Greeter : public QObject
32class Greeter : public QObject
33{
33{
@@ -37,18 +37,22 @@ class Greeter : public QObject
37 Q_PROPERTY(bool authenticated READ isAuthenticated NOTIFY isAuthenticatedChanged)
37 Q_PROPERTY(bool authenticated READ isAuthenticated NOTIFY isAuthenticatedChanged)
38 Q_PROPERTY(QString authenticationUser READ authenticationUser NOTIFY authenticationUserChanged)
38 Q_PROPERTY(QString authenticationUser READ authenticationUser NOTIFY authenticationUserChanged)
39 Q_PROPERTY(QString defaultSession READ defaultSessionHint CONSTANT)
39 Q_PROPERTY(QString defaultSession READ defaultSessionHint CONSTANT)
40− Q_PROPERTY(bool promptless READ promptless NOTIFY promptlessChanged)
41 Q_PROPERTY(QString selectUser READ selectUser CONSTANT)
40 Q_PROPERTY(QString selectUser READ selectUser CONSTANT)
42
41
43public:
42public:
44− explicit Greeter(QObject* parent=0);
43+ static Greeter *instance();
44+ virtual ~Greeter();
45
45
46 bool isActive() const;
46 bool isActive() const;
47 bool isAuthenticated() const;
47 bool isAuthenticated() const;
48 QString authenticationUser() const;
48 QString authenticationUser() const;
49 QString defaultSessionHint() const;
49 QString defaultSessionHint() const;
50− bool promptless() const;
51 QString selectUser() const;
50 QString selectUser() const;
51+ bool hasGuestAccount() const;
52+ bool showManualLoginHint() const;
53+ bool hideUsersHint() const;
54+
55+ PromptsModel *promptsModel();
52
56
53public Q_SLOTS:
57public Q_SLOTS:
54 void authenticate(const QString &username=QString());
58 void authenticate(const QString &username=QString());
@@ -57,21 +61,22 @@ public Q_SLOTS:
57 void setIsActive(bool isActive);
61 void setIsActive(bool isActive);
58
62
59Q_SIGNALS:
63Q_SIGNALS:
60− void showMessage(const QString &text, bool isError);
61− void showPrompt(const QString &text, bool isSecret, bool isDefaultPrompt);
62− void authenticationComplete();
63− void authenticationUserChanged(const QString &user);
64+ void authenticationUserChanged();
64 void isActiveChanged();
65 void isActiveChanged();
65 void isAuthenticatedChanged();
66 void isAuthenticatedChanged();
66− void promptlessChanged();
67 void showGreeter();
67 void showGreeter();
68 void hideGreeter();
68 void hideGreeter();
69+ void loginError(bool automatic);
70+ void loginSuccess(bool automatic);
71+ void authenticationStarted(); // useful for testing
69
72
70 // This signal is emitted by external agents like indicators, and the UI
73 // This signal is emitted by external agents like indicators, and the UI
71 // should switch to this user if possible.
74 // should switch to this user if possible.
72 void requestAuthenticationUser(const QString &user);
75 void requestAuthenticationUser(const QString &user);
73
76
74protected:
77protected:
78+ explicit Greeter(QObject* parent=0);
79+
75 GreeterPrivate * const d_ptr;
80 GreeterPrivate * const d_ptr;
76
81
77 Q_DECLARE_PRIVATE(Greeter)
82 Q_DECLARE_PRIVATE(Greeter)
@@ -80,6 +85,5 @@ private Q_SLOTS:
80 void showMessageFilter(const QString &text, QLightDM::Greeter::MessageType type);
85 void showMessageFilter(const QString &text, QLightDM::Greeter::MessageType type);
81 void showPromptFilter(const QString &text, QLightDM::Greeter::PromptType type);
86 void showPromptFilter(const QString &text, QLightDM::Greeter::PromptType type);
82 void authenticationCompleteFilter();
87 void authenticationCompleteFilter();
88+ void checkAuthenticationUser();
83};
89};
84−
85−#endif
plugins/LightDM/GreeterPrivate.h+13−8
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2015-2017 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -14,10 +14,14 @@
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 */
16
16
17−#ifndef UNITY_GREETER_PRIVATE_H
17+#pragma once
18−#define UNITY_GREETER_PRIVATE_H
19
18
19+#include "PromptsModel.h"
20+#include <QObject>
21+
20−#include <QLightDM/Greeter>
22+namespace QLightDM {
23+ class Greeter;
24+}
21
25
22class GreeterPrivate
26class GreeterPrivate
23{
27{
@@ -26,8 +30,11 @@ public:
26
30
27 QLightDM::Greeter *m_greeter;
31 QLightDM::Greeter *m_greeter;
28 bool m_active;
32 bool m_active;
29− bool wasPrompted;
33+ PromptsModel prompts;
30− bool promptless;
34+ PromptsModel leftovers; // prompts to show during next auth for same user
35+ bool responded;
36+ bool everResponded;
37+ QString cachedAuthUser;
31
38
32protected:
39protected:
33 Greeter * const q_ptr;
40 Greeter * const q_ptr;
@@ -35,5 +42,3 @@ protected:
35private:
42private:
36 Q_DECLARE_PUBLIC(Greeter)
43 Q_DECLARE_PUBLIC(Greeter)
37};
44};
38−
39−#endif // UNITY_GREETER_PRIVATE_H
plugins/LightDM/IntegratedLightDM/liblightdm/Greeter.cpp+7−4
@@ -49,7 +49,7 @@ QString Greeter::authenticationUser() const
49
49
50bool Greeter::hasGuestAccountHint() const
50bool Greeter::hasGuestAccountHint() const
51{
51{
52− return true;
52+ return false;
53}
53}
54
54
55QString Greeter::getHint(const QString &name) const
55QString Greeter::getHint(const QString &name) const
@@ -70,12 +70,12 @@ bool Greeter::hideUsersHint() const
70
70
71bool Greeter::showManualLoginHint() const
71bool Greeter::showManualLoginHint() const
72{
72{
73− return true;
73+ return false;
74}
74}
75
75
76bool Greeter::showRemoteLoginHint() const
76bool Greeter::showRemoteLoginHint() const
77{
77{
78− return true;
78+ return false;
79}
79}
80
80
81QString Greeter::selectUserHint() const
81QString Greeter::selectUserHint() const
@@ -146,7 +146,10 @@ void Greeter::authenticateRemote(const QString &session, const QString &username
146}
146}
147
147
148void Greeter::cancelAuthentication()
148void Greeter::cancelAuthentication()
149−{}
149+{
150+ Q_D(Greeter);
151+ d->cancelAuthentication();
152+}
150
153
151void Greeter::setLanguage (const QString &language)
154void Greeter::setLanguage (const QString &language)
152{
155{
plugins/LightDM/IntegratedLightDM/liblightdm/GreeterPrivate.cpp+27−22
@@ -206,6 +206,28 @@ public Q_SLOTS:
206 }
206 }
207 }
207 }
208
208
209+ void cancelPam()
210+ {
211+ if (pamHandle != nullptr) {
212+ QFuture<int> pamFuture = futureWatcher.future();
213+ pam_handle *handle = pamHandle;
214+ pamHandle = nullptr; // to disable normal finishPam() handling
215+ pamFuture.cancel();
216+
217+ // Note the empty loop, we just want to clear the futures queue.
218+ // Any further prompts from the pam thread will be immediately
219+ // responded to/dismissed in handlePrompt().
220+ while (respond(QString()));
221+
222+ // Now let signal/slot handling happen so the thread can finish
223+ while (!pamFuture.isFinished()) {
224+ QCoreApplication::processEvents();
225+ }
226+
227+ pam_end(handle, PAM_CONV_ERR);
228+ }
229+ }
230+
209Q_SIGNALS:
231Q_SIGNALS:
210 void showMessage(pam_handle *handle, QString text, QLightDM::Greeter::MessageType type);
232 void showMessage(pam_handle *handle, QString text, QLightDM::Greeter::MessageType type);
211 void showPrompt(pam_handle *handle, QString text, QLightDM::Greeter::PromptType type, QLightDM::GreeterImpl::ResponseFuture response);
233 void showPrompt(pam_handle *handle, QString text, QLightDM::Greeter::PromptType type, QLightDM::GreeterImpl::ResponseFuture response);
@@ -247,28 +269,6 @@ private Q_SLOTS:
247 }
269 }
248
270
249private:
271private:
250− void cancelPam()
251− {
252− if (pamHandle != nullptr) {
253− QFuture<int> pamFuture = futureWatcher.future();
254− pam_handle *handle = pamHandle;
255− pamHandle = nullptr; // to disable normal finishPam() handling
256− pamFuture.cancel();
257−
258− // Note the empty loop, we just want to clear the futures queue.
259− // Any further prompts from the pam thread will be immediately
260− // responded to/dismissed in handlePrompt().
261− while (respond(QString()));
262−
263− // Now let signal/slot handling happen so the thread can finish
264− while (!pamFuture.isFinished()) {
265− QCoreApplication::processEvents();
266− }
267−
268− pam_end(handle, PAM_CONV_ERR);
269− }
270− }
271−
272 Greeter *greeter;
272 Greeter *greeter;
273 GreeterPrivate *greeterPrivate;
273 GreeterPrivate *greeterPrivate;
274 pam_handle* pamHandle;
274 pam_handle* pamHandle;
@@ -299,6 +299,11 @@ void GreeterPrivate::handleRespond(const QString &response)
299 m_impl->respond(response);
299 m_impl->respond(response);
300}
300}
301
301
302+void GreeterPrivate::cancelAuthentication()
303+{
304+ m_impl->cancelPam();
305+}
306+
302}
307}
303
308
304#include "GreeterPrivate.moc"
309#include "GreeterPrivate.moc"
plugins/LightDM/IntegratedLightDM/liblightdm/GreeterPrivate.h+1
@@ -38,6 +38,7 @@ public:
38
38
39 void handleAuthenticate();
39 void handleAuthenticate();
40 void handleRespond(const QString &response);
40 void handleRespond(const QString &response);
41+ void cancelAuthentication();
41
42
42protected:
43protected:
43 GreeterImpl *m_impl; // if the backend needs more private data
44 GreeterImpl *m_impl; // if the backend needs more private data
plugins/LightDM/PromptsModel.cppnew file+97
@@ -0,0 +1,97 @@
1+/*
2+ * Copyright (C) 2017 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+
18+#include "PromptsModel.h"
19+
20+PromptsModel::PromptsModel(QObject* parent)
21+ : QAbstractListModel(parent)
22+{
23+ m_roleNames[TypeRole] = "type";
24+ m_roleNames[TextRole] = "text";
25+}
26+
27+PromptsModel& PromptsModel::operator=(const PromptsModel &other)
28+{
29+ beginResetModel();
30+ m_prompts = other.m_prompts;
31+ endResetModel();
32+ Q_EMIT countChanged();
33+ return *this;
34+}
35+
36+int PromptsModel::rowCount(const QModelIndex &parent) const
37+{
38+ if (parent.isValid())
39+ return 0;
40+
41+ return m_prompts.size();
42+}
43+
44+QVariant PromptsModel::data(const QModelIndex &index, int role) const
45+{
46+ if (!index.isValid() || index.column() > 0 || index.row() >= m_prompts.size())
47+ return QVariant();
48+
49+ switch (role) {
50+ case Qt::DisplayRole: // fallthrough
51+ case TextRole: return m_prompts[index.row()].prompt;
52+ case TypeRole: return m_prompts[index.row()].type;
53+ default: return QVariant();
54+ }
55+}
56+
57+QHash<int, QByteArray> PromptsModel::roleNames() const
58+{
59+ return m_roleNames;
60+}
61+
62+void PromptsModel::prepend(const QString &text, PromptType type)
63+{
64+ beginInsertRows(QModelIndex(), 0, 0);
65+ m_prompts.prepend(PromptInfo{text, type});
66+ endInsertRows();
67+
68+ Q_EMIT countChanged();
69+}
70+
71+void PromptsModel::append(const QString &text, PromptType type)
72+{
73+ beginInsertRows(QModelIndex(), m_prompts.size(), m_prompts.size());
74+ m_prompts.append(PromptInfo{text, type});
75+ endInsertRows();
76+
77+ Q_EMIT countChanged();
78+}
79+
80+void PromptsModel::clear()
81+{
82+ beginResetModel();
83+ m_prompts.clear();
84+ endResetModel();
85+
86+ Q_EMIT countChanged();
87+}
88+
89+bool PromptsModel::hasPrompt() const
90+{
91+ Q_FOREACH(const PromptInfo &info, m_prompts) {
92+ if (info.type == PromptType::Secret || info.type == PromptType::Question) {
93+ return true;
94+ }
95+ }
96+ return false;
97+}
plugins/LightDM/PromptsModel.hnew file+70
@@ -0,0 +1,70 @@
1+/*
2+ * Copyright (C) 2017 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+
18+#pragma once
19+
20+#include <QAbstractListModel>
21+
22+class PromptsModel : public QAbstractListModel
23+{
24+ Q_OBJECT
25+
26+ Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
27+
28+public:
29+ enum PromptsModelRoles {
30+ TypeRole = Qt::UserRole,
31+ TextRole,
32+ };
33+ Q_ENUM(PromptsModelRoles)
34+
35+ enum PromptType {
36+ Message,
37+ Error,
38+ Secret,
39+ Question,
40+ Button,
41+ };
42+ Q_ENUM(PromptType)
43+
44+ explicit PromptsModel(QObject* parent=0);
45+
46+ PromptsModel& operator=(const PromptsModel &other);
47+
48+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
49+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
50+ QHash<int, QByteArray> roleNames() const override;
51+
52+ Q_INVOKABLE void prepend(const QString &text, PromptType type);
53+ Q_INVOKABLE void append(const QString &text, PromptType type);
54+
55+ void clear();
56+
57+ bool hasPrompt() const;
58+
59+Q_SIGNALS:
60+ void countChanged();
61+
62+private:
63+ struct PromptInfo {
64+ QString prompt;
65+ PromptType type;
66+ };
67+
68+ QList<PromptInfo> m_prompts;
69+ QHash<int, QByteArray> m_roleNames;
70+};
plugins/LightDM/SessionsModel.cpp+1−2
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2015-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -128,7 +128,6 @@ SessionsModel::SessionsModel(QObject* parent)
128 }
128 }
129
129
130 setModel(m_model);
130 setModel(m_model);
131− setSourceModel(m_model);
132 setSortCaseSensitivity(Qt::CaseInsensitive);
131 setSortCaseSensitivity(Qt::CaseInsensitive);
133 setSortLocaleAware(true);
132 setSortLocaleAware(true);
134 setSortRole(Qt::DisplayRole);
133 setSortRole(Qt::DisplayRole);
plugins/LightDM/SessionsModel.h+4−5
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2015-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -15,8 +15,7 @@
15 *
15 *
16 */
16 */
17
17
18−#ifndef UNITY_SESSIONSMODEL_H
18+#pragma once
19−#define UNITY_SESSIONSMODEL_H
20
19
21#include <unitysortfilterproxymodelqml.h>
20#include <unitysortfilterproxymodelqml.h>
22#include <QLightDM/SessionsModel>
21#include <QLightDM/SessionsModel>
@@ -30,6 +29,7 @@ class SessionsModel : public UnitySortFilterProxyModelQML
30
29
31 Q_PROPERTY(QList<QUrl> iconSearchDirectories READ iconSearchDirectories
30 Q_PROPERTY(QList<QUrl> iconSearchDirectories READ iconSearchDirectories
32 WRITE setIconSearchDirectories NOTIFY iconSearchDirectoriesChanged)
31 WRITE setIconSearchDirectories NOTIFY iconSearchDirectoriesChanged)
32+
33Q_SIGNALS:
33Q_SIGNALS:
34 void iconSearchDirectoriesChanged();
34 void iconSearchDirectoriesChanged();
35
35
@@ -54,6 +54,7 @@ public:
54 Q_INVOKABLE QUrl iconUrl(const QString sessionName) const;
54 Q_INVOKABLE QUrl iconUrl(const QString sessionName) const;
55
55
56 void setIconSearchDirectories(const QList<QUrl> searchDirectories);
56 void setIconSearchDirectories(const QList<QUrl> searchDirectories);
57+
57private:
58private:
58 QLightDM::SessionsModel* m_model;
59 QLightDM::SessionsModel* m_model;
59 QHash<int, QByteArray> m_roleNames;
60 QHash<int, QByteArray> m_roleNames;
@@ -63,5 +64,3 @@ private:
63 QUrl("/usr/share/unity-greeter/")};
64 QUrl("/usr/share/unity-greeter/")};
64
65
65};
66};
66−
67−#endif // UNITY_SESSIONSMODEL_H
plugins/LightDM/UsersModel.cpp+171−9
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013,2015-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -12,20 +12,22 @@
12 *
12 *
13 * You should have received a copy of the GNU General Public License
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/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Author: Michael Terry <michael.terry@canonical.com>
17 */
15 */
18
16
17+#include "Greeter.h"
19#include "UsersModel.h"
18#include "UsersModel.h"
19+#include <QIdentityProxyModel>
20#include <QLightDM/UsersModel>
20#include <QLightDM/UsersModel>
21−#include <QtCore/QSortFilterProxyModel>
21+
22+#include <libintl.h>
22
23
23// First, we define an internal class that wraps LightDM's UsersModel. This
24// First, we define an internal class that wraps LightDM's UsersModel. This
24// class will modify some of the data coming from LightDM. For example, we
25// class will modify some of the data coming from LightDM. For example, we
25−// modify any empty Real Names into just normal Names.
26+// modify any empty Real Names into just normal Names. We also add optional
27+// rows, depending on configuration.
26// (We can't modify the data directly in UsersModel below because it won't sort
28// (We can't modify the data directly in UsersModel below because it won't sort
27// using the modified data.)
29// using the modified data.)
28−class MangleModel : public QSortFilterProxyModel
30+class MangleModel : public QIdentityProxyModel
29{
31{
30 Q_OBJECT
32 Q_OBJECT
31
33
@@ -33,21 +35,75 @@ public:
33 explicit MangleModel(QObject* parent=0);
35 explicit MangleModel(QObject* parent=0);
34
36
35 QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
37 QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
38+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
39+ QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
40+
41+private:
42+ struct CustomRow {
43+ QString name;
44+ QString realName;
45+ };
46+
47+ int sourceRowCount() const;
48+
49+ void updateGuestRow();
50+ void updateManualRow();
51+ void updateCustomRows();
52+
53+ void addCustomRow(const CustomRow &newRow);
54+ void removeCustomRow(const QString &rowName);
55+
56+ QList<CustomRow> m_customRows;
57+ bool m_updatingCustomRows;
36};
58};
37
59
38MangleModel::MangleModel(QObject* parent)
60MangleModel::MangleModel(QObject* parent)
39− : QSortFilterProxyModel(parent)
61+ : QIdentityProxyModel(parent)
62+ , m_updatingCustomRows(false)
40{
63{
41 setSourceModel(new QLightDM::UsersModel(this));
64 setSourceModel(new QLightDM::UsersModel(this));
65+
66+ updateCustomRows();
67+
68+ // Would be nice if there were a rowCountChanged signal in the base class.
69+ // We redo all custom rows on any row count change, because (A) some of
70+ // custom rows (manual login) use row count information and (B) when
71+ // testing, we use a modelReset signal as a way to indicate that a custom
72+ // row has been toggled off or on.
73+ connect(this, &QIdentityProxyModel::modelReset,
74+ this, &MangleModel::updateCustomRows);
75+ connect(this, &QIdentityProxyModel::rowsInserted,
76+ this, &MangleModel::updateCustomRows);
77+ connect(this, &QIdentityProxyModel::rowsRemoved,
78+ this, &MangleModel::updateCustomRows);
42}
79}
43
80
44QVariant MangleModel::data(const QModelIndex &index, int role) const
81QVariant MangleModel::data(const QModelIndex &index, int role) const
45{
82{
46− QVariant variantData = QSortFilterProxyModel::data(index, role);
83+ QVariant variantData;
84+
85+ if (index.row() >= rowCount())
86+ return QVariant();
87+
88+ bool isCustomRow = index.row() >= sourceRowCount();
89+ if (isCustomRow && index.column() == 0) {
90+ int customIndex = index.row() - sourceRowCount();
91+ if (role == QLightDM::UsersModel::NameRole) {
92+ variantData = m_customRows[customIndex].name;
93+ } else if (role == QLightDM::UsersModel::RealNameRole) {
94+ variantData = m_customRows[customIndex].realName;
95+ } else if (role == QLightDM::UsersModel::LoggedInRole) {
96+ variantData = false;
97+ } else if (role == QLightDM::UsersModel::SessionRole) {
98+ variantData = Greeter::instance()->defaultSessionHint();
99+ }
100+ } else {
101+ variantData = QIdentityProxyModel::data(index, role);
102+ }
47
103
48 // If user's real name is empty, switch to unix name
104 // If user's real name is empty, switch to unix name
49 if (role == QLightDM::UsersModel::RealNameRole && variantData.toString().isEmpty()) {
105 if (role == QLightDM::UsersModel::RealNameRole && variantData.toString().isEmpty()) {
50− variantData = QSortFilterProxyModel::data(index, QLightDM::UsersModel::NameRole);
106+ variantData = data(index, QLightDM::UsersModel::NameRole);
51 } else if (role == QLightDM::UsersModel::BackgroundPathRole && variantData.toString().startsWith('#')) {
107 } else if (role == QLightDM::UsersModel::BackgroundPathRole && variantData.toString().startsWith('#')) {
52 const QString stringData = "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='" + variantData.toString() + "'/></svg>";
108 const QString stringData = "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='" + variantData.toString() + "'/></svg>";
53 variantData = stringData;
109 variantData = stringData;
@@ -56,6 +112,95 @@ QVariant MangleModel::data(const QModelIndex &index, int role) const
56 return variantData;
112 return variantData;
57}
113}
58
114
115+void MangleModel::addCustomRow(const CustomRow &newRow)
116+{
117+ for (int i = 0; i < m_customRows.size(); i++) {
118+ if (m_customRows[i].name == newRow.name) {
119+ return; // we don't have custom rows that change content yet
120+ }
121+ }
122+
123+ beginInsertRows(QModelIndex(), rowCount(), rowCount());
124+ m_customRows << newRow;
125+ endInsertRows();
126+}
127+
128+void MangleModel::removeCustomRow(const QString &rowName)
129+{
130+ for (int i = 0; i < m_customRows.size(); i++) {
131+ if (m_customRows[i].name == rowName) {
132+ int rowNum = sourceRowCount() + i;
133+ beginRemoveRows(QModelIndex(), rowNum, rowNum);
134+ m_customRows.removeAt(i);
135+ endRemoveRows();
136+ break;
137+ }
138+ }
139+}
140+
141+void MangleModel::updateManualRow()
142+{
143+ bool hasAnotherEntry = sourceRowCount() > 0;
144+ for (int i = 0; !hasAnotherEntry && i < m_customRows.size(); i++) {
145+ if (m_customRows[i].name != QStringLiteral("*other")) {
146+ hasAnotherEntry = true;
147+ }
148+ }
149+
150+ // Show manual login if we are asked to OR if no other entry exists
151+ if (Greeter::instance()->showManualLoginHint() || !hasAnotherEntry)
152+ addCustomRow({QStringLiteral("*other"), gettext("Login")});
153+ else
154+ removeCustomRow(QStringLiteral("*other"));
155+}
156+
157+void MangleModel::updateGuestRow()
158+{
159+ if (Greeter::instance()->hasGuestAccount())
160+ addCustomRow({QStringLiteral("*guest"), gettext("Guest Session")});
161+ else
162+ removeCustomRow(QStringLiteral("*guest"));
163+}
164+
165+void MangleModel::updateCustomRows()
166+{
167+ // We update when rowCount changes, but we also insert/remove rows here.
168+ // So guard this function to avoid recursion.
169+ if (m_updatingCustomRows)
170+ return;
171+
172+ m_updatingCustomRows = true;
173+ updateGuestRow();
174+ updateManualRow();
175+ m_updatingCustomRows = false;
176+}
177+
178+int MangleModel::rowCount(const QModelIndex &parent) const
179+{
180+ if (parent.isValid())
181+ return 0;
182+ else
183+ return sourceRowCount() + m_customRows.size();
184+}
185+
186+int MangleModel::sourceRowCount() const
187+{
188+ return Greeter::instance()->hideUsersHint() ? 0 : sourceModel()->rowCount();
189+}
190+
191+QModelIndex MangleModel::index(int row, int column, const QModelIndex &parent) const
192+{
193+ if (row >= rowCount())
194+ return QModelIndex();
195+
196+ bool isCustomRow = row >= sourceRowCount();
197+ if (isCustomRow && !parent.isValid()) {
198+ return createIndex(row, column);
199+ } else {
200+ return QIdentityProxyModel::index(row, column, parent);
201+ }
202+}
203+
59// **** Now we continue with actual UsersModel class ****
204// **** Now we continue with actual UsersModel class ****
60
205
61UsersModel::UsersModel(QObject* parent)
206UsersModel::UsersModel(QObject* parent)
@@ -68,4 +213,21 @@ UsersModel::UsersModel(QObject* parent)
68 sort(0);
213 sort(0);
69}
214}
70
215
216+bool UsersModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
217+{
218+ auto leftName = source_left.data(QLightDM::UsersModel::NameRole);
219+ auto rightName = source_right.data(QLightDM::UsersModel::NameRole);
220+
221+ if (leftName == QStringLiteral("*guest"))
222+ return false;
223+ if (rightName == QStringLiteral("*guest"))
224+ return true;
225+ if (leftName == QStringLiteral("*other"))
226+ return false;
227+ if (rightName == QStringLiteral("*other"))
228+ return true;
229+
230+ return UnitySortFilterProxyModelQML::lessThan(source_left, source_right);
231+}
232+
71#include "UsersModel.moc"
233#include "UsersModel.moc"
plugins/LightDM/UsersModel.h+6−8
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2012,2013 Canonical, Ltd.
2+ * Copyright (C) 2012-2013,2015-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -12,19 +12,16 @@
12 *
12 *
13 * You should have received a copy of the GNU General Public License
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/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Authors: Michael Terry <michael.terry@canonical.com>
17 */
15 */
18
16
19/* This class is a really tiny filter around QLightDM::UsersModel. There are
17/* This class is a really tiny filter around QLightDM::UsersModel. There are
20 some operations that we want to edit a bit for the benefit of Qml.
18 some operations that we want to edit a bit for the benefit of Qml.
21 Specifically, we want to sort users according to realName. */
19 Specifically, we want to sort users according to realName. */
22
20
23−#ifndef UNITY_USERSMODEL_H
21+#pragma once
24−#define UNITY_USERSMODEL_H
25
22
26#include <unitysortfilterproxymodelqml.h>
23#include <unitysortfilterproxymodelqml.h>
27−#include <QtCore/QObject>
24+#include <QObject>
28
25
29class UsersModel : public UnitySortFilterProxyModelQML
26class UsersModel : public UnitySortFilterProxyModelQML
30{
27{
@@ -32,6 +29,7 @@ class UsersModel : public UnitySortFilterProxyModelQML
32
29
33public:
30public:
34 explicit UsersModel(QObject* parent=0);
31 explicit UsersModel(QObject* parent=0);
35−};
36
32
37−#endif
33+protected:
34+ bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override;
35+};
plugins/LightDM/plugin.cpp+12−4
@@ -19,6 +19,7 @@
19#include "DBusGreeter.h"
19#include "DBusGreeter.h"
20#include "DBusGreeterList.h"
20#include "DBusGreeterList.h"
21#include "Greeter.h"
21#include "Greeter.h"
22+#include "PromptsModel.h"
22#include "SessionsModel.h"
23#include "SessionsModel.h"
23#include "UsersModel.h"
24#include "UsersModel.h"
24#include <libusermetricsoutput/ColorTheme.h>
25#include <libusermetricsoutput/ColorTheme.h>
@@ -32,16 +33,24 @@
32
33
33static QObject *greeter_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
34static QObject *greeter_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
34{
35{
35− Q_UNUSED(engine)
36 Q_UNUSED(scriptEngine)
36 Q_UNUSED(scriptEngine)
37
37
38− Greeter *greeter = new Greeter();
38+ Greeter *greeter = Greeter::instance();
39 new DBusGreeter(greeter, QStringLiteral("/"));
39 new DBusGreeter(greeter, QStringLiteral("/"));
40 new DBusGreeterList(greeter, QStringLiteral("/list"));
40 new DBusGreeterList(greeter, QStringLiteral("/list"));
41
41
42+ engine->setObjectOwnership(greeter, QQmlEngine::CppOwnership);
43+
42 return greeter;
44 return greeter;
43}
45}
44
46
47+static QObject *prompts_provider(QQmlEngine *engine, QJSEngine *)
48+{
49+ auto model = Greeter::instance()->promptsModel();
50+ engine->setObjectOwnership(model, QQmlEngine::CppOwnership);
51+ return model;
52+}
53+
45static QObject *sessions_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
54static QObject *sessions_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
46{
55{
47 Q_UNUSED(engine)
56 Q_UNUSED(engine)
@@ -78,8 +87,7 @@ void PLUGIN_CLASSNAME::registerTypes(const char *uri)
78 #error No library defined in LightDM plugin
87 #error No library defined in LightDM plugin
79#endif
88#endif
80
89
81− qRegisterMetaType<QLightDM::Greeter::MessageType>("QLightDM::Greeter::MessageType");
90+ qmlRegisterSingletonType<PromptsModel>(uri, 0, 1, "Prompts", prompts_provider);
82− qRegisterMetaType<QLightDM::Greeter::PromptType>("QLightDM::Greeter::PromptType");
83
91
84 qmlRegisterSingletonType<SessionsModel>(uri, 0, 1, "Sessions", sessions_provider);
92 qmlRegisterSingletonType<SessionsModel>(uri, 0, 1, "Sessions", sessions_provider);
85 qmlRegisterUncreatableType<QLightDM::SessionsModel>(uri, 0, 1, "SessionRoles", QStringLiteral("Type is not instantiable"));
93 qmlRegisterUncreatableType<QLightDM::SessionsModel>(uri, 0, 1, "SessionRoles", QStringLiteral("Type is not instantiable"));
plugins/Unity/Launcher/launchermodel.cpp−1
@@ -214,7 +214,6 @@ void LauncherModel::quickListActionInvoked(const QString &appId, int actionIndex
214void LauncherModel::setUser(const QString &username)
214void LauncherModel::setUser(const QString &username)
215{
215{
216 Q_UNUSED(username)
216 Q_UNUSED(username)
217− qWarning() << "This backend doesn't support multiple users";
218}
217}
219
218
220QString LauncherModel::getUrlForAppId(const QString &appId) const
219QString LauncherModel::getUrlForAppId(const QString &appId) const
plugins/Utils/CMakeLists.txt+1
@@ -34,6 +34,7 @@ set(QMLPLUGIN_SRC
34 deviceconfigparser.cpp
34 deviceconfigparser.cpp
35 globalfunctions.cpp
35 globalfunctions.cpp
36 URLDispatcher.cpp
36 URLDispatcher.cpp
37+ tabfocusfence.cpp
37 plugin.cpp
38 plugin.cpp
38 )
39 )
39
40
plugins/Utils/appdrawerproxymodel.cpp+1−1
@@ -185,5 +185,5 @@ QString AppDrawerProxyModel::appId(int index) const
185 return adpm->appId(sourceIndex.row());
185 return adpm->appId(sourceIndex.row());
186 }
186 }
187 }
187 }
188− return nullptr;
188+ return QString();
189}
189}
plugins/Utils/plugin.cpp+3−1
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2012-2015 Canonical, Ltd.
2+ * Copyright (C) 2012-2017 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -40,6 +40,7 @@
40#include "globalfunctions.h"
40#include "globalfunctions.h"
41#include "URLDispatcher.h"
41#include "URLDispatcher.h"
42#include "appdrawerproxymodel.h"
42#include "appdrawerproxymodel.h"
43+#include "tabfocusfence.h"
43
44
44static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
45static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
45{
46{
@@ -84,4 +85,5 @@ void UtilsPlugin::registerTypes(const char *uri)
84 qmlRegisterSingletonType<GlobalFunctions>(uri, 0, 1, "Functions", createGlobalFunctions);
85 qmlRegisterSingletonType<GlobalFunctions>(uri, 0, 1, "Functions", createGlobalFunctions);
85 qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher");
86 qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher");
86 qmlRegisterType<AppDrawerProxyModel>(uri, 0, 1, "AppDrawerProxyModel");
87 qmlRegisterType<AppDrawerProxyModel>(uri, 0, 1, "AppDrawerProxyModel");
88+ qmlRegisterType<TabFocusFenceItem>(uri, 0, 1, "TabFocusFence");
87}
89}
plugins/Utils/tabfocusfence.cppnew file+72
@@ -0,0 +1,72 @@
1+/*
2+ * Copyright 2017 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+*/
16+
17+#include "tabfocusfence.h"
18+
19+#include <private/qquickitem_p.h>
20+
21+TabFocusFenceItem::TabFocusFenceItem(QQuickItem *parent) : QQuickItem(parent)
22+{
23+ QQuickItemPrivate *d = QQuickItemPrivate::get(this);
24+ d->isTabFence = true;
25+ setFlag(ItemIsFocusScope);
26+}
27+
28+bool TabFocusFenceItem::focusNext()
29+{
30+ QQuickItem * current = scopedFocusItem();
31+ if (current) {
32+ QQuickItem * next = current->nextItemInFocusChain(true);
33+ if (next) {
34+ next->setFocus(true, Qt::TabFocusReason);
35+ return true;
36+ }
37+ }
38+ return false;
39+}
40+
41+bool TabFocusFenceItem::focusPrev()
42+{
43+ QQuickItem * current = scopedFocusItem();
44+ if (current) {
45+ QQuickItem * prev = current->nextItemInFocusChain(false);
46+ if (prev) {
47+ prev->setFocus(true, Qt::BacktabFocusReason);
48+ return true;
49+ }
50+ }
51+ return false;
52+}
53+
54+void TabFocusFenceItem::keyPressEvent(QKeyEvent *event)
55+{
56+ // Needed so we eat Tab keys when there's only one item inside the fence
57+ if (event->key() == Qt::Key_Tab) {
58+ event->accept();
59+ } else {
60+ QQuickItem::keyPressEvent(event);
61+ }
62+}
63+
64+void TabFocusFenceItem::keyReleaseEvent(QKeyEvent *event)
65+{
66+ // Needed so we eat Tab keys when there's only one item inside the fence
67+ if (event->key() == Qt::Key_Tab) {
68+ event->accept();
69+ } else {
70+ QQuickItem::keyReleaseEvent(event);
71+ }
72+}
plugins/Utils/tabfocusfence.hnew file+38
@@ -0,0 +1,38 @@
1+/*
2+ * Copyright 2017 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+*/
16+
17+#ifndef TABFOCUSFENCE_H
18+#define TABFOCUSFENCE_H
19+
20+#include <QQuickItem>
21+
22+// An item that restricts focus Tab travelling
23+// to its children
24+class TabFocusFenceItem : public QQuickItem
25+{
26+Q_OBJECT
27+
28+public:
29+ TabFocusFenceItem(QQuickItem *parent = nullptr);
30+
31+ Q_INVOKABLE bool focusNext();
32+ Q_INVOKABLE bool focusPrev();
33+
34+ void keyPressEvent(QKeyEvent *event) override;
35+ void keyReleaseEvent(QKeyEvent *event) override;
36+};
37+
38+#endif
po/unity8.pot+67−39
@@ -8,7 +8,7 @@ msgid ""
8msgstr ""
8msgstr ""
9"Project-Id-Version: unity8\n"
9"Project-Id-Version: unity8\n"
10"Report-Msgid-Bugs-To: \n"
10"Report-Msgid-Bugs-To: \n"
11−"POT-Creation-Date: 2017-01-10 14:48+0000\n"
11+"POT-Creation-Date: 2017-01-24 07:46+0000\n"
12"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14"Language-Team: LANGUAGE <LL@li.org>\n"
14"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -18,10 +18,38 @@ msgstr ""
18"Content-Transfer-Encoding: 8bit\n"
18"Content-Transfer-Encoding: 8bit\n"
19"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
19"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
20
20
21−#: plugins/LightDM/Greeter.cpp:123
21+#: plugins/LightDM/Greeter.cpp:188
22msgid "Password: "
22msgid "Password: "
23msgstr ""
23msgstr ""
24
24
25+#: plugins/LightDM/Greeter.cpp:203
26+msgid "Username"
27+msgstr ""
28+
29+#: plugins/LightDM/Greeter.cpp:242
30+msgid "Failed to authenticate"
31+msgstr ""
32+
33+#: plugins/LightDM/Greeter.cpp:244
34+msgid "Invalid password, please try again"
35+msgstr ""
36+
37+#: plugins/LightDM/Greeter.cpp:254
38+msgid "Log In"
39+msgstr ""
40+
41+#: plugins/LightDM/Greeter.cpp:254
42+msgid "Retry"
43+msgstr ""
44+
45+#: plugins/LightDM/UsersModel.cpp:152
46+msgid "Login"
47+msgstr ""
48+
49+#: plugins/LightDM/UsersModel.cpp:160
50+msgid "Guest Session"
51+msgstr ""
52+
25#: plugins/Unity/Launcher/launcheritem.cpp:50
53#: plugins/Unity/Launcher/launcheritem.cpp:50
26#: plugins/Unity/Launcher/launcheritem.cpp:122
54#: plugins/Unity/Launcher/launcheritem.cpp:122
27msgid "Pin shortcut"
55msgid "Pin shortcut"
@@ -35,66 +63,66 @@ msgstr ""
35msgid "Unpin shortcut"
63msgid "Unpin shortcut"
36msgstr ""
64msgstr ""
37
65
38−#: qml/Components/Dialogs.qml:149
66+#: qml/Components/Dialogs.qml:177
39msgctxt "Title: Lock/Log out dialog"
67msgctxt "Title: Lock/Log out dialog"
40msgid "Log out"
68msgid "Log out"
41msgstr ""
69msgstr ""
42
70
43−#: qml/Components/Dialogs.qml:150
71+#: qml/Components/Dialogs.qml:178
44msgid "Are you sure you want to log out?"
72msgid "Are you sure you want to log out?"
45msgstr ""
73msgstr ""
46
74
47−#: qml/Components/Dialogs.qml:152
75+#: qml/Components/Dialogs.qml:181
48msgctxt "Button: Lock the system"
76msgctxt "Button: Lock the system"
49msgid "Lock"
77msgid "Lock"
50msgstr ""
78msgstr ""
51
79
52−#: qml/Components/Dialogs.qml:160
80+#: qml/Components/Dialogs.qml:191
53msgctxt "Button: Log out from the system"
81msgctxt "Button: Log out from the system"
54msgid "Log Out"
82msgid "Log Out"
55msgstr ""
83msgstr ""
56
84
57−#: qml/Components/Dialogs.qml:167 qml/Components/Dialogs.qml:225
85+#: qml/Components/Dialogs.qml:199 qml/Components/Dialogs.qml:264
58−#: qml/Dash/DashPageHeader.qml:324 qml/Greeter/NarrowView.qml:232
86+#: qml/Dash/DashPageHeader.qml:324 qml/Greeter/NarrowView.qml:222
59#: qml/Wizard/Pages/passcode-confirm.qml:32
87#: qml/Wizard/Pages/passcode-confirm.qml:32
60#: qml/Wizard/Pages/passcode-set.qml:32
88#: qml/Wizard/Pages/passcode-set.qml:32
61msgid "Cancel"
89msgid "Cancel"
62msgstr ""
90msgstr ""
63
91
64−#: qml/Components/Dialogs.qml:179
92+#: qml/Components/Dialogs.qml:211
65msgctxt "Title: Reboot dialog"
93msgctxt "Title: Reboot dialog"
66msgid "Reboot"
94msgid "Reboot"
67msgstr ""
95msgstr ""
68
96
69−#: qml/Components/Dialogs.qml:180
97+#: qml/Components/Dialogs.qml:212
70msgid "Are you sure you want to reboot?"
98msgid "Are you sure you want to reboot?"
71msgstr ""
99msgstr ""
72
100
73−#: qml/Components/Dialogs.qml:182
101+#: qml/Components/Dialogs.qml:215
74msgid "No"
102msgid "No"
75msgstr ""
103msgstr ""
76
104
77−#: qml/Components/Dialogs.qml:189
105+#: qml/Components/Dialogs.qml:223
78msgid "Yes"
106msgid "Yes"
79msgstr ""
107msgstr ""
80
108
81−#: qml/Components/Dialogs.qml:204
109+#: qml/Components/Dialogs.qml:239
82msgctxt "Title: Power off/Restart dialog"
110msgctxt "Title: Power off/Restart dialog"
83msgid "Power"
111msgid "Power"
84msgstr ""
112msgstr ""
85
113
86−#: qml/Components/Dialogs.qml:205
114+#: qml/Components/Dialogs.qml:240
87msgid ""
115msgid ""
88"Are you sure you would like\n"
116"Are you sure you would like\n"
89"to power off?"
117"to power off?"
90msgstr ""
118msgstr ""
91
119
92−#: qml/Components/Dialogs.qml:208
120+#: qml/Components/Dialogs.qml:244
93msgctxt "Button: Power off the system"
121msgctxt "Button: Power off the system"
94msgid "Power off"
122msgid "Power off"
95msgstr ""
123msgstr ""
96
124
97−#: qml/Components/Dialogs.qml:217
125+#: qml/Components/Dialogs.qml:255
98msgctxt "Button: Restart the system"
126msgctxt "Button: Restart the system"
99msgid "Restart"
127msgid "Restart"
100msgstr ""
128msgstr ""
@@ -120,7 +148,7 @@ msgid "Alt + PrtScr"
120msgstr ""
148msgstr ""
121
149
122#: qml/Components/KeyboardShortcutsOverlay.qml:80
150#: qml/Components/KeyboardShortcutsOverlay.qml:80
123−msgid "Takes a screenshot of a window."
151+msgid "Takes a screenshot of the current window."
124msgstr ""
152msgstr ""
125
153
126#: qml/Components/KeyboardShortcutsOverlay.qml:88
154#: qml/Components/KeyboardShortcutsOverlay.qml:88
@@ -267,7 +295,7 @@ msgstr ""
267msgid "Closes the current window."
295msgid "Closes the current window."
268msgstr ""
296msgstr ""
269
297
270−#: qml/Components/Lockscreen.qml:212 qml/Greeter/NarrowView.qml:252
298+#: qml/Components/Lockscreen.qml:212 qml/Greeter/NarrowView.qml:242
271msgid "Return to Call"
299msgid "Return to Call"
272msgstr ""
300msgstr ""
273
301
@@ -275,7 +303,7 @@ msgstr ""
275msgid "Emergency Call"
303msgid "Emergency Call"
276msgstr ""
304msgstr ""
277
305
278−#: qml/Components/Lockscreen.qml:244
306+#: qml/Components/Lockscreen.qml:245
279msgid "OK"
307msgid "OK"
280msgstr ""
308msgstr ""
281
309
@@ -289,29 +317,29 @@ msgstr ""
289msgid "%1:%2"
317msgid "%1:%2"
290msgstr ""
318msgstr ""
291
319
292−#: qml/Components/ModeSwitchWarningDialog.qml:32
320+#: qml/Components/ModeSwitchWarningDialog.qml:33
293msgid "Apps may have unsaved data:"
321msgid "Apps may have unsaved data:"
294msgstr ""
322msgstr ""
295
323
296−#: qml/Components/ModeSwitchWarningDialog.qml:57
324+#: qml/Components/ModeSwitchWarningDialog.qml:60
297msgctxt ""
325msgctxt ""
298"Re-dock means connect the device again to an external screen/mouse/keyboard"
326"Re-dock means connect the device again to an external screen/mouse/keyboard"
299msgid "Re-dock, save your work and close these apps to continue."
327msgid "Re-dock, save your work and close these apps to continue."
300msgstr ""
328msgstr ""
301
329
302−#: qml/Components/ModeSwitchWarningDialog.qml:63
330+#: qml/Components/ModeSwitchWarningDialog.qml:67
303msgid "Or force close now (unsaved data will be lost)."
331msgid "Or force close now (unsaved data will be lost)."
304msgstr ""
332msgstr ""
305
333
306−#: qml/Components/ModeSwitchWarningDialog.qml:75
334+#: qml/Components/ModeSwitchWarningDialog.qml:80
307msgid "OK, I will reconnect"
335msgid "OK, I will reconnect"
308msgstr ""
336msgstr ""
309
337
310−#: qml/Components/ModeSwitchWarningDialog.qml:76
338+#: qml/Components/ModeSwitchWarningDialog.qml:81
311msgid "Reconnect now!"
339msgid "Reconnect now!"
312msgstr ""
340msgstr ""
313
341
314−#: qml/Components/ModeSwitchWarningDialog.qml:88
342+#: qml/Components/ModeSwitchWarningDialog.qml:94
315msgid "Close all"
343msgid "Close all"
316msgstr ""
344msgstr ""
317
345
@@ -461,44 +489,44 @@ msgid_plural "Please wait %1 minutes and then try again…"
461msgstr[0] ""
489msgstr[0] ""
462msgstr[1] ""
490msgstr[1] ""
463
491
464−#: qml/Greeter/Greeter.qml:592
492+#: qml/Greeter/Greeter.qml:561
465msgid "Try again"
493msgid "Try again"
466msgstr ""
494msgstr ""
467
495
468−#: qml/Greeter/LoginList.qml:70
496+#: qml/Greeter/Greeter.qml:562
469−msgid "Passphrase"
497+msgid "Enter passphrase to unlock"
470msgstr ""
498msgstr ""
471
499
472−#: qml/Greeter/LoginList.qml:71
500+#: qml/Greeter/Greeter.qml:563
473−msgid "Passcode"
501+msgid "Enter passcode to unlock"
474msgstr ""
502msgstr ""
475
503
476−#: qml/Greeter/LoginList.qml:98
504+#: qml/Greeter/NarrowView.qml:242
477−msgid "Retry"
505+msgid "Emergency"
478msgstr ""
506msgstr ""
479
507
480−#: qml/Greeter/LoginList.qml:99
508+#: qml/Greeter/PromptList.qml:126
481−msgid "Log In"
509+msgid "Passphrase"
482msgstr ""
510msgstr ""
483
511
484−#: qml/Greeter/NarrowView.qml:252
512+#: qml/Greeter/PromptList.qml:126
485−msgid "Emergency"
513+msgid "Passcode"
486msgstr ""
514msgstr ""
487
515
488#: qml/Greeter/SessionsList.qml:122
516#: qml/Greeter/SessionsList.qml:122
489msgid "Select desktop environment"
517msgid "Select desktop environment"
490msgstr ""
518msgstr ""
491
519
492−#: qml/Launcher/Drawer.qml:80
520+#: qml/Launcher/Drawer.qml:92
493msgid "Search…"
521msgid "Search…"
494msgstr ""
522msgstr ""
495
523
496−#: qml/Launcher/Drawer.qml:101
524+#: qml/Launcher/Drawer.qml:123
497msgctxt "Apps sorted alphabetically"
525msgctxt "Apps sorted alphabetically"
498msgid "A-Z"
526msgid "A-Z"
499msgstr ""
527msgstr ""
500
528
501−#: qml/Launcher/MoreAppsHeader.qml:32
529+#: qml/Launcher/MoreAppsHeader.qml:39
502msgid "More apps in the store"
530msgid "More apps in the store"
503msgstr ""
531msgstr ""
504
532
qml/ApplicationMenus/MenuBar.qml+14
@@ -57,6 +57,20 @@ Item {
57 onReleased: d.stopSHortcutTimer()
57 onReleased: d.stopSHortcutTimer()
58 }
58 }
59
59
60+ GlobalShortcut {
61+ shortcut: Qt.AltModifier | Qt.Key_F10
62+ active: enableKeyFilter && d.currentItem == null
63+ onTriggered: {
64+ for (var i = 0; i < rowRepeater.count; i++) {
65+ var item = rowRepeater.itemAt(i);
66+ if (item.enabled) {
67+ item.show();
68+ break;
69+ }
70+ }
71+ }
72+ }
73+
60 InverseMouseArea {
74 InverseMouseArea {
61 acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
75 acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
62 anchors.fill: parent
76 anchors.fill: parent
qml/ApplicationMenus/MenuPopup.qml+162−123
@@ -26,19 +26,7 @@ UbuntuShape {
26 objectName: "menu"
26 objectName: "menu"
27 backgroundColor: theme.palette.normal.overlay
27 backgroundColor: theme.palette.normal.overlay
28
28
29− property alias unityMenuModel: listView.model
29+ property alias unityMenuModel: repeater.model
30−
31− readonly property real __ajustedMinimumHeight: {
32− if (listView.contentHeight > __minimumHeight) {
33− return units.gu(30);
34− }
35− return Math.max(listView.contentHeight, units.gu(2));
36− }
37−
38− readonly property real __minimumWidth: units.gu(20)
39− readonly property real __minimumHeight: units.gu(30)
40− readonly property real __maximumWidth: Screen.width * 0.7
41− readonly property real __maximumHeight: Screen.height * 0.7
42
30
43 function show() {
31 function show() {
44 visible = true;
32 visible = true;
@@ -63,18 +51,23 @@ UbuntuShape {
63 d.dismissAll();
51 d.dismissAll();
64 }
52 }
65
53
66− implicitWidth: container.width
54+ implicitWidth: focusScope.width
67− implicitHeight: MathUtils.clamp(listView.contentHeight, __ajustedMinimumHeight, __maximumHeight)
55+ implicitHeight: focusScope.height
68
56
69 MenuNavigator {
57 MenuNavigator {
70 id: d
58 id: d
71 objectName: "d"
59 objectName: "d"
72− itemView: listView
60+ itemView: repeater
73
61
74 property Item currentItem: null
62 property Item currentItem: null
75 property Item hoveredItem: null
63 property Item hoveredItem: null
76 readonly property int currentIndex: currentItem ? currentItem.__ownIndex : -1
64 readonly property int currentIndex: currentItem ? currentItem.__ownIndex : -1
77
65
66+ property real __minimumWidth: units.gu(20)
67+ property real __maximumWidth: Screen.width * 0.7
68+ property real __minimumHeight: units.gu(2)
69+ property real __maximumHeight: Screen.height * 0.7
70+
78 signal dismissAll()
71 signal dismissAll()
79
72
80 onCurrentItemChanged: {
73 onCurrentItemChanged: {
@@ -86,13 +79,26 @@ UbuntuShape {
86 }
79 }
87
80
88 onSelect: {
81 onSelect: {
89− currentItem = listView.itemAt(index);
82+ currentItem = repeater.itemAt(index);
83+ if (currentItem) {
84+ if (currentItem.y < listView.contentY) {
85+ listView.contentY = currentItem.y;
86+ } else if (currentItem.y + currentItem.height > listView.contentY + listView.height) {
87+ listView.contentY = currentItem.y + currentItem.height - listView.height;
88+ }
89+ }
90 }
90 }
91 }
91 }
92
92
93+ MouseArea {
94+ // Eat events.
95+ anchors.fill: parent
96+ }
97+
93 Item {
98 Item {
94 id: focusScope
99 id: focusScope
95− anchors.fill: parent
100+ width: container.width
101+ height: container.height
96 focus: visible
102 focus: visible
97
103
98 Keys.onUpPressed: d.selectPrevious(d.currentIndex)
104 Keys.onUpPressed: d.selectPrevious(d.currentIndex)
@@ -108,17 +114,17 @@ UbuntuShape {
108 id: container
114 id: container
109 objectName: "container"
115 objectName: "container"
110
116
111− width: listView.contentWidth
117+ height: MathUtils.clamp(listView.contentHeight, d.__minimumHeight, d.__maximumHeight)
112− height: parent.height
118+ width: menuColumn.width
113 spacing: 0
119 spacing: 0
114
120
115− // FIXME use ListView.header - tried but was flaky with positionViewAtIndex.
121+ // Header - scroll up
116 Item {
122 Item {
117− Layout.fillWidth: true;
123+ Layout.fillWidth: true
118− Layout.maximumHeight: units.gu(3)
124+ height: units.gu(3)
119− Layout.minimumHeight: units.gu(3)
120 visible: listView.contentHeight > root.height
125 visible: listView.contentHeight > root.height
121 enabled: !listView.atYBeginning
126 enabled: !listView.atYBeginning
127+ z: 1
122
128
123 Rectangle {
129 Rectangle {
124 color: enabled ? theme.palette.normal.overlayText :
130 color: enabled ? theme.palette.normal.overlayText :
@@ -143,47 +149,35 @@ UbuntuShape {
143 MouseArea {
149 MouseArea {
144 anchors.fill: parent
150 anchors.fill: parent
145 onPressed: {
151 onPressed: {
146− var index = listView.indexAt(0, listView.contentY);
152+ var item = menuColumn.childAt(0, listView.contentY);
147− listView.positionViewAtIndex(index-1, ListView.Beginning);
153+ if (item) {
154+ var previousItem = item;
155+ do {
156+ previousItem = repeater.itemAt(previousItem.__ownIndex-1);
157+ if (!previousItem) {
158+ listView.contentY = 0;
159+ return;
160+ }
161+ } while (previousItem.__isSeparator);
162+
163+ listView.contentY = previousItem.y
164+ }
148 }
165 }
149 }
166 }
150 }
167 }
151
168
152− ListView {
169+ // Menu Items
170+ Flickable {
153 id: listView
171 id: listView
154− objectName: "listView"
155− Layout.fillHeight: true
156− Layout.fillWidth: true
157− contentWidth: MathUtils.clamp(contentItem.childrenRect.width,
158− __minimumWidth,
159− __maximumWidth)
160−
161− orientation: Qt.Vertical
162− interactive: contentHeight > height
163 clip: interactive
172 clip: interactive
164− highlightFollowsCurrentItem: false
165−
166− highlight: Rectangle {
167− color: "transparent"
168− border.width: units.dp(1)
169− border.color: UbuntuColors.orange
170− z: 1
171
173
172− width: listView.width
174+ Layout.fillHeight: true
173− height: d.currentItem ? d.currentItem.height : 0
175+ Layout.fillWidth: true
174− y: d.currentItem ? d.currentItem.y : 0
176+ contentHeight: menuColumn.height
175− visible: d.currentItem
177+ interactive: height < contentHeight
176− }
177−
178− function itemAt(index) {
179− if (index > count || index < 0) return null;
180− currentIndex = index;
181− return currentItem;
182− }
183
178
184 MouseArea {
179 MouseArea {
185− id: menuMouseArea
186− anchors.fill: listView
180+ anchors.fill: parent
187 hoverEnabled: true
181 hoverEnabled: true
188 propagateComposedEvents: true // propogate events so we send clicks to children.
182 propagateComposedEvents: true // propogate events so we send clicks to children.
189 z: 1 // on top so we override any other hovers
183 z: 1 // on top so we override any other hovers
@@ -195,7 +189,7 @@ UbuntuShape {
195
189
196 if (!d.hoveredItem || !d.currentItem ||
190 if (!d.hoveredItem || !d.currentItem ||
197 !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) {
191 !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) {
198− d.hoveredItem = listView.itemAt(listView.indexAt(pos.x, pos.y));
192+ d.hoveredItem = menuColumn.childAt(pos.x, pos.y)
199 if (!d.hoveredItem || !d.hoveredItem.enabled)
193 if (!d.hoveredItem || !d.hoveredItem.enabled)
200 return false;
194 return false;
201 d.currentItem = d.hoveredItem;
195 d.currentItem = d.hoveredItem;
@@ -216,89 +210,123 @@ UbuntuShape {
216 }
210 }
217 }
211 }
218
212
219− delegate: Loader {
213+ ColumnLayout {
220− id: loader
214+ id: menuColumn
221− objectName: root.objectName + "-item" + __ownIndex
215+ spacing: 0
222
216
223− property int __ownIndex: index
217+ width: MathUtils.clamp(implicitWidth, d.__minimumWidth, d.__maximumWidth)
224
218
225− width: root.width
219+ Repeater {
226− enabled: model.isSeparator ? false : model.sensitive
220+ id: repeater
227
221
228− sourceComponent: {
222+ Loader {
229− if (model.isSeparator) {
223+ id: loader
230− return separatorComponent;
224+ objectName: root.objectName + "-item" + __ownIndex
231− }
232− return menuItemComponent;
233− }
234
225
235− property Item popup: null
226+ property int __ownIndex: index
236−
227+ property bool __isSeparator: model.isSeparator
237− Component {
228+
238− id: menuItemComponent
229+ enabled: __isSeparator ? false : model.sensitive
239− MenuItem {
230+
240− id: menuItem
231+ sourceComponent: {
241− menuData: model
232+ if (model.isSeparator) {
242− objectName: loader.objectName + "-actionItem"
233+ return separatorComponent;
243−
244− action.onTriggered: {
245− d.currentItem = loader;
246−
247− if (hasSubmenu) {
248− if (!popup) {
249− var model = root.unityMenuModel.submenu(__ownIndex);
250− popup = submenuComponent.createObject(focusScope, {
251− objectName: loader.objectName + "-",
252− unityMenuModel: model,
253− x: Qt.binding(function() { return root.width }),
254− y: Qt.binding(function() { return loader.y })
255− });
256− } else if (popup) {
257− popup.visible = true;
258− }
259− popup.retreat.connect(function() {
260− popup.destroy();
261− popup = null;
262− menuItem.forceActiveFocus();
263− })
264− } else {
265− root.unityMenuModel.activate(__ownIndex);
266 }
234 }
235+ return menuItemComponent;
267 }
236 }
268
237
269− Connections {
238+ property Item popup: null
270− target: d
239+
271− onCurrentIndexChanged: {
240+ Layout.fillWidth: true
272− if (popup && d.currentIndex != __ownIndex) {
241+
242+ Component {
243+ id: menuItemComponent
244+ MenuItem {
245+ id: menuItem
246+ menuData: model
247+ objectName: loader.objectName + "-actionItem"
248+
249+ width: MathUtils.clamp(implicitWidth, d.__minimumWidth, d.__maximumWidth)
250+
251+ action.onTriggered: {
252+ d.currentItem = loader;
253+
254+ if (hasSubmenu) {
255+ if (!popup) {
256+ var model = root.unityMenuModel.submenu(__ownIndex);
257+ popup = submenuComponent.createObject(focusScope, {
258+ objectName: loader.objectName + "-",
259+ unityMenuModel: model,
260+ x: Qt.binding(function() { return root.width }),
261+ y: Qt.binding(function() {
262+ var dummy = listView.contentY; // force a recalc on contentY change.
263+ return mapToItem(container, 0, y).y;
264+ })
265+ });
266+ } else if (popup) {
273− popup.visible = false;
267+ popup.visible = true;
268+ }
269+ popup.retreat.connect(function() {
270+ popup.destroy();
271+ popup = null;
272+ menuItem.forceActiveFocus();
273+ })
274+ } else {
275+ root.unityMenuModel.activate(__ownIndex);
276+ }
274 }
277 }
278+
279+ Connections {
280+ target: d
281+ onCurrentIndexChanged: {
282+ if (popup && d.currentIndex != __ownIndex) {
283+ popup.visible = false;
284+ }
275− }
285+ }
276− onDismissAll: {
286+ onDismissAll: {
277− if (popup) {
287+ if (popup) {
278− popup.destroy();
288+ popup.destroy();
279− popup = null;
289+ popup = null;
290+ }
291+ }
280 }
292 }
281 }
293 }
282 }
294 }
283− }
284− }
285
295
286− Component {
296+ Component {
287− id: separatorComponent
297+ id: separatorComponent
288− ListItems.ThinDivider {
298+ ListItems.ThinDivider {
289− objectName: loader.objectName + "-separator"
299+ objectName: loader.objectName + "-separator"
300+ implicitHeight: units.dp(2)
301+ }
302+ }
290 }
303 }
304+
291 }
305 }
292 }
306 }
293− } // ListView
294
307
295− // FIXME use ListView.footer - tried but was flaky with positionViewAtIndex.
308+ // Highlight
309+ Rectangle {
310+ color: "transparent"
311+ border.width: units.dp(1)
312+ border.color: UbuntuColors.orange
313+ z: 1
314+
315+ width: listView.width
316+ height: d.currentItem ? d.currentItem.height : 0
317+ y: d.currentItem ? d.currentItem.y : 0
318+ visible: d.currentItem
319+ }
320+
321+ } // Flickable
322+
323+ // Header - scroll down
296 Item {
324 Item {
297− Layout.fillWidth: true;
325+ Layout.fillWidth: true
298− Layout.maximumHeight: units.gu(3)
326+ height: units.gu(3)
299− Layout.minimumHeight: units.gu(3)
300 visible: listView.contentHeight > root.height
327 visible: listView.contentHeight > root.height
301 enabled: !listView.atYEnd
328 enabled: !listView.atYEnd
329+ z: 1
302
330
303 Rectangle {
331 Rectangle {
304 color: enabled ? theme.palette.normal.overlayText :
332 color: enabled ? theme.palette.normal.overlayText :
@@ -323,8 +351,19 @@ UbuntuShape {
323 MouseArea {
351 MouseArea {
324 anchors.fill: parent
352 anchors.fill: parent
325 onPressed: {
353 onPressed: {
326− var index = listView.indexAt(0, listView.contentY);
354+ var item = menuColumn.childAt(0, listView.contentY + listView.height);
327− listView.positionViewAtIndex(index+1, ListView.Beginning);
355+ if (item) {
356+ var nextItem = item;
357+ do {
358+ nextItem = repeater.itemAt(nextItem.__ownIndex+1);
359+ if (!nextItem) {
360+ listView.contentY = listView.contentHeight - listView.height;
361+ return;
362+ }
363+ } while (nextItem.__isSeparator);
364+
365+ listView.contentY = nextItem.y - listView.height
366+ }
328 }
367 }
329 }
368 }
330 }
369 }
qml/Components/Dialogs.qml+42−3
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2014-2016 Canonical, Ltd.
2+ * Copyright (C) 2014-2017 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -24,10 +24,13 @@ import Unity.Platform 1.0
24import Utils 0.1
24import Utils 0.1
25import "../Greeter"
25import "../Greeter"
26
26
27−Item {
27+MouseArea {
28 id: root
28 id: root
29+ acceptedButtons: Qt.AllButtons
30+ hoverEnabled: true
31+ onWheel: wheel.accepted = true
29
32
30− readonly property alias hasActiveDialog: dialogLoader.active
33+ readonly property bool hasActiveDialog: dialogLoader.active || d.modeSwitchWarningPopup
31
34
32 // to be set from outside, useful mostly for testing purposes
35 // to be set from outside, useful mostly for testing purposes
33 property var unitySessionService: DBusUnitySessionService
36 property var unitySessionService: DBusUnitySessionService
@@ -42,6 +45,7 @@ Item {
42 }
45 }
43 property string usageScenario
46 property string usageScenario
44 property size screenSize: Qt.size(Screen.width, Screen.height)
47 property size screenSize: Qt.size(Screen.width, Screen.height)
48+ property bool hasKeyboard: false
45
49
46 signal powerOffClicked();
50 signal powerOffClicked();
47
51
@@ -120,6 +124,11 @@ Item {
120 onTriggered: LightDMService.greeter.showGreeter()
124 onTriggered: LightDMService.greeter.showGreeter()
121 }
125 }
122
126
127+ GlobalShortcut { // lock screen
128+ shortcut: Qt.MetaModifier|Qt.Key_L
129+ onTriggered: LightDMService.greeter.showGreeter()
130+ }
131+
123 QtObject {
132 QtObject {
124 id: d // private stuff
133 id: d // private stuff
125 objectName: "dialogsPrivate"
134 objectName: "dialogsPrivate"
@@ -140,6 +149,25 @@ Item {
140 objectName: "dialogLoader"
149 objectName: "dialogLoader"
141 anchors.fill: parent
150 anchors.fill: parent
142 active: false
151 active: false
152+ onActiveChanged: {
153+ if (!active) {
154+ if (previousFocusedItem) {
155+ previousFocusedItem.forceActiveFocus(Qt.OtherFocusReason);
156+ previousFocusedItem = undefined;
157+ }
158+ previousSourceComponent = undefined;
159+ sourceComponent = undefined;
160+ }
161+ }
162+ onSourceComponentChanged: {
163+ if (previousSourceComponent !== sourceComponent) {
164+ previousSourceComponent = sourceComponent;
165+ previousFocusedItem = window.activeFocusItem;
166+ }
167+ }
168+
169+ property var previousSourceComponent: undefined
170+ property var previousFocusedItem: undefined
143 }
171 }
144
172
145 Component {
173 Component {
@@ -149,13 +177,16 @@ Item {
149 title: i18n.ctr("Title: Lock/Log out dialog", "Log out")
177 title: i18n.ctr("Title: Lock/Log out dialog", "Log out")
150 text: i18n.tr("Are you sure you want to log out?")
178 text: i18n.tr("Are you sure you want to log out?")
151 Button {
179 Button {
180+ width: parent.width
152 text: i18n.ctr("Button: Lock the system", "Lock")
181 text: i18n.ctr("Button: Lock the system", "Lock")
153 onClicked: {
182 onClicked: {
154 LightDMService.greeter.showGreeter()
183 LightDMService.greeter.showGreeter()
155 logoutDialog.hide();
184 logoutDialog.hide();
156 }
185 }
186+ Component.onCompleted: if (root.hasKeyboard) forceActiveFocus(Qt.TabFocusReason)
157 }
187 }
158 Button {
188 Button {
189+ width: parent.width
159 focus: true
190 focus: true
160 text: i18n.ctr("Button: Log out from the system", "Log Out")
191 text: i18n.ctr("Button: Log out from the system", "Log Out")
161 onClicked: {
192 onClicked: {
@@ -164,6 +195,7 @@ Item {
164 }
195 }
165 }
196 }
166 Button {
197 Button {
198+ width: parent.width
167 text: i18n.tr("Cancel")
199 text: i18n.tr("Cancel")
168 onClicked: {
200 onClicked: {
169 logoutDialog.hide();
201 logoutDialog.hide();
@@ -179,12 +211,14 @@ Item {
179 title: i18n.ctr("Title: Reboot dialog", "Reboot")
211 title: i18n.ctr("Title: Reboot dialog", "Reboot")
180 text: i18n.tr("Are you sure you want to reboot?")
212 text: i18n.tr("Are you sure you want to reboot?")
181 Button {
213 Button {
214+ width: parent.width
182 text: i18n.tr("No")
215 text: i18n.tr("No")
183 onClicked: {
216 onClicked: {
184 rebootDialog.hide();
217 rebootDialog.hide();
185 }
218 }
186 }
219 }
187 Button {
220 Button {
221+ width: parent.width
188 focus: true
222 focus: true
189 text: i18n.tr("Yes")
223 text: i18n.tr("Yes")
190 onClicked: {
224 onClicked: {
@@ -193,6 +227,7 @@ Item {
193 rebootDialog.hide();
227 rebootDialog.hide();
194 }
228 }
195 color: theme.palette.normal.negative
229 color: theme.palette.normal.negative
230+ Component.onCompleted: if (root.hasKeyboard) forceActiveFocus(Qt.TabFocusReason)
196 }
231 }
197 }
232 }
198 }
233 }
@@ -204,6 +239,7 @@ Item {
204 title: i18n.ctr("Title: Power off/Restart dialog", "Power")
239 title: i18n.ctr("Title: Power off/Restart dialog", "Power")
205 text: i18n.tr("Are you sure you would like\nto power off?")
240 text: i18n.tr("Are you sure you would like\nto power off?")
206 Button {
241 Button {
242+ width: parent.width
207 focus: true
243 focus: true
208 text: i18n.ctr("Button: Power off the system", "Power off")
244 text: i18n.ctr("Button: Power off the system", "Power off")
209 onClicked: {
245 onClicked: {
@@ -212,8 +248,10 @@ Item {
212 root.powerOffClicked();
248 root.powerOffClicked();
213 }
249 }
214 color: theme.palette.normal.negative
250 color: theme.palette.normal.negative
251+ Component.onCompleted: if (root.hasKeyboard) forceActiveFocus(Qt.TabFocusReason)
215 }
252 }
216 Button {
253 Button {
254+ width: parent.width
217 text: i18n.ctr("Button: Restart the system", "Restart")
255 text: i18n.ctr("Button: Restart the system", "Restart")
218 onClicked: {
256 onClicked: {
219 root.closeAllApps();
257 root.closeAllApps();
@@ -222,6 +260,7 @@ Item {
222 }
260 }
223 }
261 }
224 Button {
262 Button {
263+ width: parent.width
225 text: i18n.tr("Cancel")
264 text: i18n.tr("Cancel")
226 onClicked: {
265 onClicked: {
227 powerDialog.hide();
266 powerDialog.hide();
qml/Components/KeyboardShortcutsOverlay.qml+1−1
@@ -77,7 +77,7 @@ Rectangle {
77 font.weight: Font.Medium
77 font.weight: Font.Medium
78 }
78 }
79 Label {
79 Label {
80− text: i18n.tr("Takes a screenshot of a window.")
80+ text: i18n.tr("Takes a screenshot of the current window.")
81 fontSize: "small"
81 fontSize: "small"
82 font.weight: Font.Light
82 font.weight: Font.Light
83 wrapMode: Text.Wrap
83 wrapMode: Text.Wrap
qml/Components/KeymapSwitcher.qml+15−7
@@ -51,6 +51,9 @@ QtObject {
51 nextIndex = currentKeymapIndex + 1;
51 nextIndex = currentKeymapIndex + 1;
52 }
52 }
53 currentKeymapIndex = nextIndex;
53 currentKeymapIndex = nextIndex;
54+ if (actionGroup.currentAction.valid) {
55+ actionGroup.currentAction.updateState(currentKeymapIndex);
56+ }
54 }
57 }
55
58
56 function previousKeymap() {
59 function previousKeymap() {
@@ -60,30 +63,35 @@ QtObject {
60 prevIndex = currentKeymapIndex - 1;
63 prevIndex = currentKeymapIndex - 1;
61 }
64 }
62 currentKeymapIndex = prevIndex;
65 currentKeymapIndex = prevIndex;
66+ if (actionGroup.currentAction.valid) {
67+ actionGroup.currentAction.updateState(currentKeymapIndex);
68+ }
63 }
69 }
64
70
65− property Binding surfaceKeymapBinding: Binding {
71+ property Binding surfaceKeymapBinding: Binding { // NB: needed mainly for xmir & libertine apps
66 target: root.focusedSurface
72 target: root.focusedSurface
67 property: "keymap"
73 property: "keymap"
68 value: root.currentKeymap
74 value: root.currentKeymap
69 }
75 }
70
76
77+ property Binding unityKeymapBinding: Binding {
78+ target: Mir
79+ property: "currentKeymap"
80+ value: root.currentKeymap
81+ }
82+
71 // indicator
83 // indicator
72 property QDBusActionGroup actionGroup: QDBusActionGroup {
84 property QDBusActionGroup actionGroup: QDBusActionGroup {
73 busType: DBus.SessionBus
85 busType: DBus.SessionBus
74 busName: "com.canonical.indicator.keyboard"
86 busName: "com.canonical.indicator.keyboard"
75 objectPath: "/com/canonical/indicator/keyboard"
87 objectPath: "/com/canonical/indicator/keyboard"
76
88
77− property variant currentAction: action("current")
89+ property variant currentAction: action("current") // the one that's checked by the indicator
78− property variant activeAction: action("active")
90+ property variant activeAction: action("active") // the one that we clicked
79
91
80 Component.onCompleted: actionGroup.start();
92 Component.onCompleted: actionGroup.start();
81 }
93 }
82
94
83− onCurrentKeymapIndexChanged: {
84− actionGroup.currentAction.updateState(currentKeymapIndex);
85− }
86−
87 readonly property int activeActionState: actionGroup.activeAction.valid ? actionGroup.activeAction.state : -1
95 readonly property int activeActionState: actionGroup.activeAction.valid ? actionGroup.activeAction.state : -1
88
96
89 onActiveActionStateChanged: {
97 onActiveActionStateChanged: {
qml/Components/Lockscreen.qml+3−1
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2017 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -240,8 +240,10 @@ Showable {
240 property var dialogLoader // dummy to satisfy ShellDialog's context dependent prop
240 property var dialogLoader // dummy to satisfy ShellDialog's context dependent prop
241
241
242 Button {
242 Button {
243+ width: parent.width
243 objectName: "infoPopupOkButton"
244 objectName: "infoPopupOkButton"
244 text: i18n.tr("OK")
245 text: i18n.tr("OK")
246+ focus: true
245 onClicked: {
247 onClicked: {
246 PopupUtils.close(dialog)
248 PopupUtils.close(dialog)
247 root.infoPopupConfirmed();
249 root.infoPopupConfirmed();
qml/Components/ModeSwitchWarningDialog.qml+8−2
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2015-2017 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -29,6 +29,7 @@ ShellDialog {
29 signal forceClose();
29 signal forceClose();
30
30
31 Label {
31 Label {
32+ width: parent.width
32 text: i18n.tr("Apps may have unsaved data:")
33 text: i18n.tr("Apps may have unsaved data:")
33 fontSize: "large"
34 fontSize: "large"
34 color: "#5D5D5D"
35 color: "#5D5D5D"
@@ -37,6 +38,7 @@ ShellDialog {
37 Repeater {
38 Repeater {
38 id: appRepeater
39 id: appRepeater
39 RowLayout {
40 RowLayout {
41+ width: parent.width
40 spacing: units.gu(2)
42 spacing: units.gu(2)
41 Image {
43 Image {
42 Layout.preferredHeight: units.gu(2)
44 Layout.preferredHeight: units.gu(2)
@@ -54,20 +56,23 @@ ShellDialog {
54 }
56 }
55
57
56 Label {
58 Label {
59+ width: parent.width
57 text: i18n.ctr("Re-dock means connect the device again to an external screen/mouse/keyboard", "Re-dock, save your work and close these apps to continue.")
60 text: i18n.ctr("Re-dock means connect the device again to an external screen/mouse/keyboard", "Re-dock, save your work and close these apps to continue.")
58 wrapMode: Text.WordWrap
61 wrapMode: Text.WordWrap
59 color: "#888888"
62 color: "#888888"
60 }
63 }
61
64
62 Label {
65 Label {
66+ width: parent.width
63 text: i18n.tr("Or force close now (unsaved data will be lost).")
67 text: i18n.tr("Or force close now (unsaved data will be lost).")
64 wrapMode: Text.WordWrap
68 wrapMode: Text.WordWrap
65 color: "#888888"
69 color: "#888888"
66 }
70 }
67
71
68− ThinDivider {}
72+ ThinDivider { width: parent.width }
69
73
70 RowLayout {
74 RowLayout {
75+ width: parent.width
71 Label {
76 Label {
72 objectName: "reconnectLabel"
77 objectName: "reconnectLabel"
73 Layout.fillWidth: true
78 Layout.fillWidth: true
@@ -84,6 +89,7 @@ ShellDialog {
84 }
89 }
85
90
86 Button {
91 Button {
92+ focus: true
87 objectName: "forceCloseButton"
93 objectName: "forceCloseButton"
88 text: i18n.tr("Close all")
94 text: i18n.tr("Close all")
89 color: theme.palette.normal.negative
95 color: theme.palette.normal.negative
qml/Components/ShellDialog.qml+21−3
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2017 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -19,6 +19,7 @@ import QtQuick 2.4
19import Ubuntu.Components 1.3
19import Ubuntu.Components 1.3
20import Ubuntu.Components.Themes 1.3
20import Ubuntu.Components.Themes 1.3
21import Ubuntu.Components.Popups 1.3
21import Ubuntu.Components.Popups 1.3
22+import Utils 0.1
22
23
23/*
24/*
24 A Dialog configured for use as a proper in-scene Dialog
25 A Dialog configured for use as a proper in-scene Dialog
@@ -32,9 +33,9 @@ Dialog {
32 // NB: PopupBase, Dialog's superclass, will check for the existence of this property
33 // NB: PopupBase, Dialog's superclass, will check for the existence of this property
33 property bool reparentToRootItem: false
34 property bool reparentToRootItem: false
34
35
35− onVisibleChanged: { if (!visible && dialogLoader) { dialogLoader.active = false; } }
36+ default property alias columnContents: column.data
36
37
37− Keys.onEscapePressed: hide()
38+ onVisibleChanged: { if (!visible && dialogLoader) { dialogLoader.active = false; } }
38
39
39 focus: true
40 focus: true
40
41
@@ -50,4 +51,21 @@ Dialog {
50 __foreground.theme = themeHack
51 __foreground.theme = themeHack
51 show();
52 show();
52 }
53 }
54+
55+ TabFocusFence {
56+ width: parent.width
57+ height: column.height
58+ focus: true
59+ Column {
60+ id: column
61+ width: parent.width
62+ spacing: units.gu(2)
63+ }
64+ Keys.onDownPressed: {
65+ event.accepted = focusNext();
66+ }
67+ Keys.onUpPressed: {
68+ event.accepted = focusPrev();
69+ }
70+ }
53}
71}
qml/Greeter/Circle.qml+1−1
@@ -27,7 +27,7 @@ Canvas {
27 onCenterCircleChanged: requestPaint()
27 onCenterCircleChanged: requestPaint()
28
28
29 onPaint: {
29 onPaint: {
30− if (circleScale <= 0) {
30+ if (circleScale <= 0 || width <= 0 || height <= 0) {
31 return;
31 return;
32 }
32 }
33
33
qml/Greeter/FullLightDMImpl.qml+1
@@ -22,6 +22,7 @@ Item{
22
22
23 property var greeter: LightDM.Greeter
23 property var greeter: LightDM.Greeter
24 property var infographic: LightDM.Infographic
24 property var infographic: LightDM.Infographic
25+ property var prompts: LightDM.Prompts
25 property var sessions: LightDM.Sessions
26 property var sessions: LightDM.Sessions
26 property var sessionRoles: LightDM.SessionRoles
27 property var sessionRoles: LightDM.SessionRoles
27 property var users: LightDM.Users
28 property var users: LightDM.Users
qml/Greeter/Greeter.qml+27−57
@@ -74,11 +74,11 @@ Showable {
74 forcedUnlock = false;
74 forcedUnlock = false;
75 if (required) {
75 if (required) {
76 if (loader.item) {
76 if (loader.item) {
77− loader.item.reset(true /* forceShow */);
77+ loader.item.forceShow();
78 }
78 }
79 // Normally loader.onLoaded will select a user, but if we're
79 // Normally loader.onLoaded will select a user, but if we're
80 // already shown, do it manually.
80 // already shown, do it manually.
81− d.selectUser(d.currentIndex, false);
81+ d.selectUser(d.currentIndex);
82 }
82 }
83
83
84 // Even though we may already be shown, we want to call show() for its
84 // Even though we may already be shown, we want to call show() for its
@@ -154,7 +154,7 @@ Showable {
154 readonly property bool multiUser: LightDMService.users.count > 1
154 readonly property bool multiUser: LightDMService.users.count > 1
155 readonly property int selectUserIndex: d.getUserIndex(LightDMService.greeter.selectUser)
155 readonly property int selectUserIndex: d.getUserIndex(LightDMService.greeter.selectUser)
156 property int currentIndex: Math.max(selectUserIndex, 0)
156 property int currentIndex: Math.max(selectUserIndex, 0)
157− property bool waiting
157+ readonly property bool waiting: LightDMService.prompts.count == 0 && !root.forcedUnlock
158 property bool isLockscreen // true when we are locking an active session, rather than first user login
158 property bool isLockscreen // true when we are locking an active session, rather than first user login
159 readonly property bool secureFingerprint: isLockscreen &&
159 readonly property bool secureFingerprint: isLockscreen &&
160 AccountsService.failedFingerprintLogins <
160 AccountsService.failedFingerprintLogins <
@@ -189,13 +189,9 @@ Showable {
189 return -1;
189 return -1;
190 }
190 }
191
191
192− function selectUser(index, reset) {
192+ function selectUser(index) {
193 if (index < 0 || index >= LightDMService.users.count)
193 if (index < 0 || index >= LightDMService.users.count)
194 return;
194 return;
195− d.waiting = true;
196− if (reset) {
197− loader.item.reset(false /* forceShow */);
198− }
199 currentIndex = index;
195 currentIndex = index;
200 var user = LightDMService.users.data(index, LightDMService.userRoles.NameRole);
196 var user = LightDMService.users.data(index, LightDMService.userRoles.NameRole);
201 AccountsService.user = user;
197 AccountsService.user = user;
@@ -206,20 +202,17 @@ Showable {
206 function hideView() {
202 function hideView() {
207 if (loader.item) {
203 if (loader.item) {
208 loader.item.enabled = false; // drop OSK and prevent interaction
204 loader.item.enabled = false; // drop OSK and prevent interaction
209− loader.item.notifyAuthenticationSucceeded(false /* showFakePassword */);
210 loader.item.hide();
205 loader.item.hide();
211 }
206 }
212 }
207 }
213
208
214 function login() {
209 function login() {
215− d.waiting = true;
216 if (LightDMService.greeter.startSessionSync(root.sessionToStart())) {
210 if (LightDMService.greeter.startSessionSync(root.sessionToStart())) {
217 sessionStarted();
211 sessionStarted();
218 hideView();
212 hideView();
219 } else if (loader.item) {
213 } else if (loader.item) {
220 loader.item.notifyAuthenticationFailed();
214 loader.item.notifyAuthenticationFailed();
221 }
215 }
222− d.waiting = false;
223 }
216 }
224
217
225 function startUnlock(toTheRight) {
218 function startUnlock(toTheRight) {
@@ -239,27 +232,13 @@ Showable {
239 }
232 }
240 }
233 }
241
234
242− function showPromptMessage(text, isError) {
243− // inefficient, but we only rarely deal with messages
244− var html = text.replace(/&/g, "&amp;")
245− .replace(/</g, "&lt;")
246− .replace(/>/g, "&gt;")
247− .replace(/\n/g, "<br>");
248− if (isError) {
249− html = "<font color=\"#df382c\">" + html + "</font>";
250− }
251−
252− if (loader.item) {
253− loader.item.showMessage(html);
254− }
255− }
256−
257 function showFingerprintMessage(msg) {
235 function showFingerprintMessage(msg) {
236+ d.selectUser(d.currentIndex);
237+ LightDMService.prompts.prepend(msg, LightDMService.prompts.Error);
258 if (loader.item) {
238 if (loader.item) {
259− loader.item.reset(false /* forceShow */);
260 loader.item.showErrorMessage(msg);
239 loader.item.showErrorMessage(msg);
240+ loader.item.notifyAuthenticationFailed();
261 }
241 }
262− showPromptMessage(msg, true);
263 }
242 }
264 }
243 }
265
244
@@ -285,7 +264,6 @@ Showable {
285
264
286 onRequiredChanged: {
265 onRequiredChanged: {
287 if (required) {
266 if (required) {
288− d.waiting = true;
289 lockedApp = "";
267 lockedApp = "";
290 }
268 }
291 }
269 }
@@ -377,14 +355,14 @@ Showable {
377 onLoaded: {
355 onLoaded: {
378 root.lockedApp = "";
356 root.lockedApp = "";
379 item.forceActiveFocus();
357 item.forceActiveFocus();
380− d.selectUser(d.currentIndex, true);
358+ d.selectUser(d.currentIndex);
381 LightDMService.infographic.readyForDataChange();
359 LightDMService.infographic.readyForDataChange();
382 }
360 }
383
361
384 Connections {
362 Connections {
385 target: loader.item
363 target: loader.item
386 onSelected: {
364 onSelected: {
387− d.selectUser(index, true);
365+ d.selectUser(index);
388 }
366 }
389 onResponded: {
367 onResponded: {
390 if (root.locked) {
368 if (root.locked) {
@@ -481,27 +459,15 @@ Showable {
481 onShowGreeter: root.forceShow()
459 onShowGreeter: root.forceShow()
482 onHideGreeter: root.forcedUnlock = true
460 onHideGreeter: root.forcedUnlock = true
483
461
484− onShowMessage: d.showPromptMessage(text, isError)
462+ onLoginError: {
485−
486− onShowPrompt: {
487− if (loader.item) {
463+ if (!loader.item) {
488− loader.item.showPrompt(text, isSecret, isDefaultPrompt);
464+ return;
489 }
465 }
490
466
491− d.waiting = false;
467+ loader.item.notifyAuthenticationFailed();
492− }
493−
494− onAuthenticationComplete: {
495− d.waiting = false;
496
468
497− if (LightDMService.greeter.authenticated) {
469+ if (!automatic) {
498− if (!LightDMService.greeter.promptless) {
499− d.login();
500− }
501− } else {
502− if (!LightDMService.greeter.promptless) {
503− AccountsService.failedLogins++;
470+ AccountsService.failedLogins++;
504− }
505
471
506 // Check if we should initiate a factory reset
472 // Check if we should initiate a factory reset
507 if (maxFailedLogins >= 2) { // require at least a warning
473 if (maxFailedLogins >= 2) { // require at least a warning
@@ -519,14 +485,17 @@ Showable {
519 forcedDelayTimer.forceDelay();
485 forcedDelayTimer.forceDelay();
520 }
486 }
521
487
522− loader.item.notifyAuthenticationFailed();
523− if (!LightDMService.greeter.promptless) {
524− d.selectUser(d.currentIndex, false);
488+ d.selectUser(d.currentIndex);
525− }
489+ }
490+ }
491+
492+ onLoginSuccess: {
493+ if (!automatic) {
494+ d.login();
526 }
495 }
527 }
496 }
528
497
529− onRequestAuthenticationUser: d.selectUser(d.getUserIndex(user), true)
498+ onRequestAuthenticationUser: d.selectUser(d.getUserIndex(user))
530 }
499 }
531
500
532 Connections {
501 Connections {
@@ -589,7 +558,9 @@ Showable {
589 if (!d.secureFingerprint) {
558 if (!d.secureFingerprint) {
590 d.startUnlock(false /* toTheRight */); // use normal login instead
559 d.startUnlock(false /* toTheRight */); // use normal login instead
591 }
560 }
592− var msg = d.secureFingerprint ? i18n.tr("Try again") : "";
561+ var msg = d.secureFingerprint ? i18n.tr("Try again") :
562+ d.alphanumeric ? i18n.tr("Enter passphrase to unlock") :
563+ i18n.tr("Enter passcode to unlock");
593 d.showFingerprintMessage(msg);
564 d.showFingerprintMessage(msg);
594 }
565 }
595
566
@@ -609,8 +580,7 @@ Showable {
609 }
580 }
610 console.log("Identified user by fingerprint:", result);
581 console.log("Identified user by fingerprint:", result);
611 if (loader.item) {
582 if (loader.item) {
612− loader.item.enabled = false;
583+ loader.item.showFakePassword();
613− loader.item.notifyAuthenticationSucceeded(true /* showFakePassword */);
614 }
584 }
615 if (root.active)
585 if (root.active)
616 root.forcedUnlock = true;
586 root.forcedUnlock = true;
qml/Greeter/GreeterPrompt.qml+32−31
@@ -27,31 +27,25 @@ FocusScope {
27 property bool isAlphanumeric
27 property bool isAlphanumeric
28 property string text
28 property string text
29 property bool isSecret
29 property bool isSecret
30+ property bool interactive: true
31+ readonly property alias enteredText: passwordInput.text
30
32
31 signal clicked()
33 signal clicked()
32 signal canceled()
34 signal canceled()
33− signal responded(string text)
35+ signal accepted()
34−
35− function reset() {
36− passwordInput.text = "";
37− fakeLabel.text = "";
38− d.enabled = true;
39− }
40
36
41 function showFakePassword() {
37 function showFakePassword() {
42 // Just a silly hack for looking like 4 pin numbers got entered, if
38 // Just a silly hack for looking like 4 pin numbers got entered, if
43 // a fingerprint was used and we happen to be using a pin. This was
39 // a fingerprint was used and we happen to be using a pin. This was
44 // a request from Design.
40 // a request from Design.
45 if (isSecret && isPrompt && !isAlphanumeric) {
41 if (isSecret && isPrompt && !isAlphanumeric) {
46− d.enabled = false;
47− text = "...."; // actual text doesn't matter
42+ passwordInput.text = "...."; // actual text doesn't matter
48 }
43 }
49 }
44 }
50
45
51 StyledItem {
46 StyledItem {
52 id: d
47 id: d
53
48
54− property bool enabled: true
55 readonly property color textColor: passwordInput.enabled ? theme.palette.normal.raisedText
49 readonly property color textColor: passwordInput.enabled ? theme.palette.normal.raisedText
56 : theme.palette.disabled.raisedText
50 : theme.palette.disabled.raisedText
57 readonly property color selectedColor: passwordInput.enabled ? theme.palette.normal.raised
51 readonly property color selectedColor: passwordInput.enabled ? theme.palette.normal.raised
@@ -60,12 +54,6 @@ FocusScope {
60 : theme.palette.disabled.raisedSecondaryText
54 : theme.palette.disabled.raisedSecondaryText
61 readonly property color errorColor: passwordInput.enabled ? theme.palette.normal.negative
55 readonly property color errorColor: passwordInput.enabled ? theme.palette.normal.negative
62 : theme.palette.disabled.negative
56 : theme.palette.disabled.negative
63−
64− onEnabledChanged: {
65− if (!enabled) {
66− fakeLabel.text = passwordInput.displayText;
67− }
68− }
69 }
57 }
70
58
71 Rectangle {
59 Rectangle {
@@ -86,15 +74,17 @@ FocusScope {
86 }
74 }
87 }
75 }
88
76
89− Rectangle {
77+ StyledItem {
90 id: promptButton
78 id: promptButton
91 objectName: "promptButton"
79 objectName: "promptButton"
92 anchors.fill: parent
80 anchors.fill: parent
93 visible: !root.isPrompt
81 visible: !root.isPrompt
82+ activeFocusOnTab: true
83+
84+ styleName: "FocusShape"
94
85
95 function triggered() {
86 function triggered() {
96− if (d.enabled) {
87+ if (root.interactive) {
97− d.enabled = false;
98 root.clicked();
88 root.clicked();
99 }
89 }
100 }
90 }
@@ -109,6 +99,7 @@ FocusScope {
109 }
99 }
110 }
100 }
111
101
102+ Keys.onSpacePressed: triggered();
112 Keys.onReturnPressed: triggered();
103 Keys.onReturnPressed: triggered();
113 Keys.onEnterPressed: triggered();
104 Keys.onEnterPressed: triggered();
114 MouseArea {
105 MouseArea {
@@ -129,6 +120,7 @@ FocusScope {
129 anchors.fill: parent
120 anchors.fill: parent
130 visible: root.isPrompt
121 visible: root.isPrompt
131 opacity: fakeLabel.visible ? 0 : 1
122 opacity: fakeLabel.visible ? 0 : 1
123+ activeFocusOnTab: true
132
124
133 validator: RegExpValidator {
125 validator: RegExpValidator {
134 regExp: root.isAlphanumeric ? /^.*$/ : /^\d{4}$/
126 regExp: root.isAlphanumeric ? /^.*$/ : /^\d{4}$/
@@ -142,15 +134,23 @@ FocusScope {
142
134
143 readonly property real frameSpacing: units.gu(0.5)
135 readonly property real frameSpacing: units.gu(0.5)
144
136
145− style: Item {
137+ style: StyledItem {
146− property color color: d.textColor
147− property color selectedTextColor: d.selectedColor
148− property color selectionColor: d.textColor
149− property color borderColor: "transparent"
150− property color backgroundColor: "transparent"
151− property color errorColor: d.errorColor
152− property real frameSpacing: passwordInput.frameSpacing
153 anchors.fill: parent
138 anchors.fill: parent
139+ styleName: "FocusShape"
140+
141+ // Properties needed by TextField
142+ readonly property color color: d.textColor
143+ readonly property color selectedTextColor: d.selectedColor
144+ readonly property color selectionColor: d.textColor
145+ readonly property color borderColor: "transparent"
146+ readonly property color backgroundColor: "transparent"
147+ readonly property color errorColor: d.errorColor
148+ readonly property real frameSpacing: styledItem.frameSpacing
149+
150+ // Properties needed by FocusShape
151+ readonly property bool enabled: styledItem.enabled
152+ readonly property bool keyNavigationFocus: styledItem.keyNavigationFocus
153+ property bool activeFocusOnTab
154 }
154 }
155
155
156 secondaryItem: [
156 secondaryItem: [
@@ -178,9 +178,8 @@ FocusScope {
178 onAccepted: respond()
178 onAccepted: respond()
179
179
180 function respond() {
180 function respond() {
181− if (d.enabled) {
181+ if (root.interactive) {
182− d.enabled = false;
182+ root.accepted();
183− root.responded(text);
184 }
183 }
185 }
184 }
186
185
@@ -194,6 +193,7 @@ FocusScope {
194 // palette color, whereas we want raisedSecondaryText.
193 // palette color, whereas we want raisedSecondaryText.
195 Label {
194 Label {
196 id: hint
195 id: hint
196+ objectName: "promptHint"
197 anchors {
197 anchors {
198 left: parent.left
198 left: parent.left
199 right: parent.right
199 right: parent.right
@@ -223,6 +223,7 @@ FocusScope {
223 anchors.leftMargin: passwordInput.frameSpacing * 2
223 anchors.leftMargin: passwordInput.frameSpacing * 2
224 anchors.rightMargin: passwordInput.frameSpacing * 2 + capsIcon.visibleWidth
224 anchors.rightMargin: passwordInput.frameSpacing * 2 + capsIcon.visibleWidth
225 color: d.drawColor
225 color: d.drawColor
226+ text: passwordInput.displayText
226− visible: root.isPrompt && !d.enabled
227+ visible: root.isPrompt && !root.interactive
227 }
228 }
228}
229}
qml/Greeter/IntegratedLightDMImpl.qml+1
@@ -22,6 +22,7 @@ Item{
22
22
23 property var greeter: LightDM.Greeter
23 property var greeter: LightDM.Greeter
24 property var infographic: LightDM.Infographic
24 property var infographic: LightDM.Infographic
25+ property var prompts: LightDM.Prompts
25 property var sessions: LightDM.Sessions
26 property var sessions: LightDM.Sessions
26 property var sessionRoles: LightDM.SessionRoles
27 property var sessionRoles: LightDM.SessionRoles
27 property var users: LightDM.Users
28 property var users: LightDM.Users
qml/Greeter/LightDMService.qml+4−4
@@ -27,6 +27,7 @@ Loader {
27
27
28 property var greeter: d.valid ? loader.item.greeter : null
28 property var greeter: d.valid ? loader.item.greeter : null
29 property var infographic: d.valid ? loader.item.infographic : null
29 property var infographic: d.valid ? loader.item.infographic : null
30+ property var prompts: d.valid ? loader.item.prompts : null
30 property var sessions: d.valid ? loader.item.sessions : null
31 property var sessions: d.valid ? loader.item.sessions : null
31 property var sessionRoles: d.valid ? loader.item.sessionRoles : null
32 property var sessionRoles: d.valid ? loader.item.sessionRoles : null
32 property var users: d.valid ? loader.item.users : null
33 property var users: d.valid ? loader.item.users : null
@@ -35,10 +36,9 @@ Loader {
35 // This trickery handles cases where applicationArguments aren't provided
36 // This trickery handles cases where applicationArguments aren't provided
36 // such as during testing
37 // such as during testing
37 property var fullLightDM: {
38 property var fullLightDM: {
38− if (typeof applicationArguments !== "undefined") {
39+ if (typeof applicationArguments === "undefined" ||
39− if (applicationArguments.mode === "greeter") {
40+ applicationArguments.mode === "greeter") {
40− return true;
41+ return true;
41− }
42 }
42 }
43 return false;
43 return false;
44 }
44 }
qml/Greeter/LoginList.qml+42−111
@@ -25,95 +25,50 @@ StyledItem {
25 focus: true
25 focus: true
26
26
27 property alias model: userList.model
27 property alias model: userList.model
28− property bool alphanumeric: true
28+ property alias alphanumeric: promptList.alphanumeric
29 property int currentIndex
29 property int currentIndex
30 property bool locked
30 property bool locked
31 property bool waiting
31 property bool waiting
32 property alias boxVerticalOffset: highlightItem.y
32 property alias boxVerticalOffset: highlightItem.y
33
33
34− readonly property alias passwordInput: passwordInput
35 readonly property int numAboveBelow: 4
34 readonly property int numAboveBelow: 4
36 readonly property int cellHeight: units.gu(5)
35 readonly property int cellHeight: units.gu(5)
37− readonly property int highlightedHeight: units.gu(15)
36+ readonly property int highlightedHeight: highlightItem.height
38 readonly property int moveDuration: UbuntuAnimation.FastDuration
37 readonly property int moveDuration: UbuntuAnimation.FastDuration
39− property string selectedSession
40 property string currentSession
38 property string currentSession
41 readonly property string currentUser: userList.currentItem.username
39 readonly property string currentUser: userList.currentItem.username
42− property bool wasPrompted: false
43
40
44− signal loginListSessionChanged(string session)
45 signal responded(string response)
41 signal responded(string response)
46 signal selected(int index)
42 signal selected(int index)
47 signal sessionChooserButtonClicked()
43 signal sessionChooserButtonClicked()
48
44
49 function tryToUnlock() {
45 function tryToUnlock() {
50− if (wasPrompted) {
51− passwordInput.forceActiveFocus();
46+ promptList.forceActiveFocus();
52− } else {
53− if (root.locked) {
54− root.selected(currentIndex);
55− } else {
56− root.responded("");
57− }
58− }
59− }
60−
61− function showMessage(html) {
62− if (infoLabel.text === "") {
63− infoLabel.text = html;
64− } else {
65− infoLabel.text += "<br>" + html;
66− }
67− }
68−
69− function showPrompt(text, isSecret, isDefaultPrompt) {
70− passwordInput.text = isDefaultPrompt ? alphanumeric ? i18n.tr("Passphrase")
71− : i18n.tr("Passcode")
72− : text;
73− passwordInput.isPrompt = true;
74− passwordInput.isSecret = isSecret;
75− passwordInput.reset();
76− wasPrompted = true;
77 }
47 }
78
48
79 function showError() {
49 function showError() {
80 wrongPasswordAnimation.start();
50 wrongPasswordAnimation.start();
81− root.resetAuthentication();
82− }
83−
84− function reset() {
85− root.resetAuthentication();
86 }
51 }
87
52
88 function showFakePassword() {
53 function showFakePassword() {
89− passwordInput.showFakePassword();
54+ promptList.interactive = false;
90− }
55+ promptList.showFakePassword();
91−
92− QtObject {
93− id: d
94−
95− function checkIfPromptless() {
96− if (!waiting && !wasPrompted) {
97− passwordInput.isPrompt = false;
98− passwordInput.text = root.locked ? i18n.tr("Retry")
99− : i18n.tr("Log In")
100− }
101− }
102 }
56 }
103
57
104− onWaitingChanged: d.checkIfPromptless()
105− onLockedChanged: d.checkIfPromptless()
106−
107 theme: ThemeSettings {
58 theme: ThemeSettings {
108 name: "Ubuntu.Components.Themes.Ambiance"
59 name: "Ubuntu.Components.Themes.Ambiance"
109 }
60 }
110
61
111 Keys.onUpPressed: {
62 Keys.onUpPressed: {
63+ if (currentIndex > 0) {
112− selected(currentIndex - 1);
64+ selected(currentIndex - 1);
65+ }
113 event.accepted = true;
66 event.accepted = true;
114 }
67 }
115 Keys.onDownPressed: {
68 Keys.onDownPressed: {
69+ if (currentIndex + 1 < model.count) {
116− selected(currentIndex + 1);
70+ selected(currentIndex + 1);
71+ }
117 event.accepted = true;
72 event.accepted = true;
118 }
73 }
119 Keys.onEscapePressed: {
74 Keys.onEscapePressed: {
@@ -135,7 +90,8 @@ StyledItem {
135 rightMargin: units.gu(2)
90 rightMargin: units.gu(2)
136 }
91 }
137
92
138− height: root.highlightedHeight
93+ height: Math.max(units.gu(15), promptList.height + units.gu(8))
94+ Behavior on height { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
139 }
95 }
140
96
141 ListView {
97 ListView {
@@ -153,14 +109,8 @@ StyledItem {
153 interactive: count > 1
109 interactive: count > 1
154
110
155 readonly property bool movingInternally: moveTimer.running || userList.moving
111 readonly property bool movingInternally: moveTimer.running || userList.moving
156− onMovingInternallyChanged: {
157− if (!movingInternally) {
158− root.selected(currentIndex);
159− }
160− }
161
112
162 onCurrentIndexChanged: {
113 onCurrentIndexChanged: {
163− root.resetAuthentication();
164 moveTimer.start();
114 moveTimer.start();
165 }
115 }
166
116
@@ -203,7 +153,10 @@ StyledItem {
203 // Add an offset to bottomMargin for any items below the highlight
153 // Add an offset to bottomMargin for any items below the highlight
204 bottomMargin: -(units.gu(4) + (parent.belowHighlight ? parent.belowOffset : 0))
154 bottomMargin: -(units.gu(4) + (parent.belowHighlight ? parent.belowOffset : 0))
205 }
155 }
206− text: realName
156+ text: userList.currentIndex === index
157+ && name === "*other"
158+ && LightDMService.greeter.authenticationUser !== ""
159+ ? LightDMService.greeter.authenticationUser : realName
207 color: userList.currentIndex !== index ? theme.palette.normal.raised
160 color: userList.currentIndex !== index ? theme.palette.normal.raised
208 : theme.palette.normal.raisedText
161 : theme.palette.normal.raisedText
209
162
@@ -309,64 +262,42 @@ StyledItem {
309 }
262 }
310 }
263 }
311
264
312− FadingLabel {
265+ PromptList {
313− id: infoLabel
266+ id: promptList
314− objectName: "infoLabel"
267+ objectName: "promptList"
315− anchors {
316− bottom: passwordInput.top
317− left: highlightItem.left
318− topMargin: units.gu(1)
319− bottomMargin: units.gu(1)
320− leftMargin: units.gu(2)
321− rightMargin: units.gu(1)
322− }
323−
324− color: theme.palette.normal.raisedText
325− width: root.width - anchors.leftMargin - anchors.rightMargin
326− fontSize: "small"
327− textFormat: Text.StyledText
328−
329− opacity: (userList.movingInternally || text == "") ? 0 : 1
330− Behavior on opacity {
331− NumberAnimation { duration: 100 }
332− }
333− }
334−
335− GreeterPrompt {
336− id: passwordInput
337− objectName: "passwordInput"
338 anchors {
268 anchors {
339 bottom: highlightItem.bottom
269 bottom: highlightItem.bottom
340 horizontalCenter: highlightItem.horizontalCenter
270 horizontalCenter: highlightItem.horizontalCenter
341 margins: units.gu(2)
271 margins: units.gu(2)
342 }
272 }
343 width: highlightItem.width - anchors.margins * 2
273 width: highlightItem.width - anchors.margins * 2
344− opacity: userList.movingInternally ? 0 : 1
345−
346− activeFocusOnTab: true
347− isAlphanumeric: root.alphanumeric
348
274
349− onClicked: root.tryToUnlock()
275+ onClicked: {
276+ interactive = false;
277+ if (root.locked) {
278+ root.selected(currentIndex);
279+ } else {
280+ root.responded("");
281+ }
282+ }
283+ onResponded: {
284+ interactive = false;
350− onResponded: root.responded(text)
285+ root.responded(text);
286+ }
287+ onCanceled: {
288+ interactive = false;
351− onCanceled: root.selected(currentIndex)
289+ root.selected(currentIndex);
352−
353− Behavior on opacity {
354− NumberAnimation { duration: 100 }
355 }
290 }
356
291
357− WrongPasswordAnimation {
292+ Connections {
358− id: wrongPasswordAnimation
293+ target: LightDMService.prompts
359− objectName: "wrongPasswordAnimation"
294+ onModelReset: promptList.interactive = true
360− target: passwordInput
361 }
295 }
362 }
296 }
363
297
364− function resetAuthentication() {
298+ WrongPasswordAnimation {
365− if (!userList.currentItem) {
299+ id: wrongPasswordAnimation
366− return;
300+ objectName: "wrongPasswordAnimation"
367− }
301+ target: promptList
368− infoLabel.text = "";
369− passwordInput.reset();
370− root.wasPrompted = false;
371 }
302 }
372}
303}
qml/Greeter/NarrowView.qml+11−21
@@ -48,14 +48,6 @@ FocusScope {
48 signal tease()
48 signal tease()
49 signal emergencyCall()
49 signal emergencyCall()
50
50
51− function showMessage(html) {
52− loginList.showMessage(html);
53− }
54−
55− function showPrompt(text, isSecret, isDefaultPrompt) {
56− loginList.showPrompt(text, isSecret, isDefaultPrompt);
57− }
58−
59 function showLastChance() {
51 function showLastChance() {
60 /* TODO: when we finish support for resetting device after too many
52 /* TODO: when we finish support for resetting device after too many
61 failed logins, we should re-add this popup.
53 failed logins, we should re-add this popup.
@@ -75,10 +67,8 @@ FocusScope {
75 coverPage.hide();
67 coverPage.hide();
76 }
68 }
77
69
78− function notifyAuthenticationSucceeded(showFakePassword) {
70+ function showFakePassword() {
79− if (showFakePassword) {
80− loginList.showFakePassword();
71+ loginList.showFakePassword();
81− }
82 }
72 }
83
73
84 function notifyAuthenticationFailed() {
74 function notifyAuthenticationFailed() {
@@ -89,11 +79,8 @@ FocusScope {
89 coverPage.showErrorMessage(msg);
79 coverPage.showErrorMessage(msg);
90 }
80 }
91
81
92− function reset(forceShow) {
82+ function forceShow() {
93− loginList.reset();
94− if (forceShow) {
95− coverPage.show();
83+ coverPage.show();
96− }
97 }
84 }
98
85
99 function tryToUnlock(toTheRight) {
86 function tryToUnlock(toTheRight) {
@@ -120,6 +107,7 @@ FocusScope {
120 objectName: "lockscreen"
107 objectName: "lockscreen"
121 anchors.fill: parent
108 anchors.fill: parent
122 shown: false
109 shown: false
110+ opacity: 0
123
111
124 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
112 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
125 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
113 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
@@ -195,10 +183,12 @@ FocusScope {
195 onClicked: hide()
183 onClicked: hide()
196
184
197 onShowProgressChanged: {
185 onShowProgressChanged: {
198− if (showProgress === 1) {
186+ if (showProgress === 0) {
199− loginList.reset();
187+ if (lockscreen.shown) {
200− } else if (showProgress === 0) {
201− loginList.tryToUnlock();
188+ loginList.tryToUnlock();
189+ } else {
190+ root.responded("");
191+ }
202 }
192 }
203 }
193 }
204
194
qml/Greeter/PromptList.qmlnew file+148
@@ -0,0 +1,148 @@
1+/*
2+ * Copyright (C) 2017 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+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+import "../Components"
20+import "." 0.1
21+
22+FocusScope {
23+ id: root
24+ height: childrenRect.height
25+
26+ property bool alphanumeric: true
27+ property bool interactive: true
28+
29+ signal responded(string text)
30+ signal clicked()
31+ signal canceled()
32+
33+ function showFakePassword() {
34+ for (var i = 0; i < repeater.count; i++) {
35+ var item = repeater.itemAt(i).item;
36+ if (item.isPrompt) {
37+ item.showFakePassword();
38+ }
39+ }
40+ }
41+
42+ QtObject {
43+ id: d
44+
45+ function sendResponse() {
46+ for (var i = 0; i < repeater.count; i++) {
47+ var item = repeater.itemAt(i).item;
48+ if (item.isPrompt) {
49+ root.responded(item.enteredText);
50+ }
51+ }
52+ }
53+ }
54+
55+ Column {
56+ width: parent.width
57+ spacing: units.gu(0.5)
58+
59+ Repeater {
60+ id: repeater
61+ model: LightDMService.prompts
62+
63+ delegate: Loader {
64+ id: loader
65+
66+ readonly property bool isLabel: model.type == LightDMService.prompts.Message ||
67+ model.type == LightDMService.prompts.Error
68+ readonly property var modelData: model
69+
70+ sourceComponent: isLabel ? infoLabel : greeterPrompt
71+
72+ onLoaded: {
73+ for (var i = 0; i < repeater.count; i++) {
74+ var item = repeater.itemAt(i);
75+ if (item && !item.isLabel) {
76+ item.focus = true;
77+ break;
78+ }
79+ }
80+ loader.item.opacity = 1;
81+ }
82+
83+ Binding {
84+ target: loader.item
85+ property: "model"
86+ value: loader.modelData
87+ }
88+ }
89+ }
90+ }
91+
92+ Component {
93+ id: infoLabel
94+
95+ FadingLabel {
96+ objectName: "infoLabel" + model.index
97+ width: root.width
98+
99+ property var model
100+ readonly property bool isPrompt: false
101+
102+ color: model.type === LightDMService.prompts.Message ? theme.palette.normal.raisedText
103+ : theme.palette.normal.negative
104+ fontSize: "small"
105+ textFormat: Text.PlainText
106+ text: model.text
107+
108+ Behavior on opacity { UbuntuNumberAnimation {} }
109+ opacity: 0
110+ }
111+ }
112+
113+ Component {
114+ id: greeterPrompt
115+
116+ GreeterPrompt {
117+ objectName: "greeterPrompt" + model.index
118+ width: root.width
119+
120+ property var model
121+
122+ interactive: root.interactive
123+ isAlphanumeric: model.text !== "" || root.alphanumeric
124+ isPrompt: model.type !== LightDMService.prompts.Button
125+ isSecret: model.type === LightDMService.prompts.Secret
126+ text: model.text ? model.text : (isAlphanumeric ? i18n.tr("Passphrase") : i18n.tr("Passcode"))
127+
128+ onClicked: root.clicked()
129+ onAccepted: {
130+ // If there is another GreeterPrompt, focus it.
131+ for (var i = model.index + 1; i < repeater.count; i++) {
132+ var item = repeater.itemAt(i).item;
133+ if (item.isPrompt) {
134+ item.forceActiveFocus();
135+ return;
136+ }
137+ }
138+
139+ // Nope we're the last one; just send our response.
140+ d.sendResponse();
141+ }
142+ onCanceled: root.canceled()
143+
144+ Behavior on opacity { UbuntuNumberAnimation {} }
145+ opacity: 0
146+ }
147+ }
148+}
qml/Greeter/WideView.qml+13−22
@@ -52,16 +52,8 @@ FocusScope {
52 loginList.showError();
52 loginList.showError();
53 }
53 }
54
54
55− function reset(forceShow) {
55+ function forceShow() {
56− loginList.reset();
56+ // Nothing to do, we are always fully shown
57− }
58−
59− function showMessage(html) {
60− loginList.showMessage(html);
61− }
62−
63− function showPrompt(text, isSecret, isDefaultPrompt) {
64− loginList.showPrompt(text, isSecret, isDefaultPrompt);
65 }
57 }
66
58
67 function tryToUnlock(toTheRight) {
59 function tryToUnlock(toTheRight) {
@@ -84,10 +76,8 @@ FocusScope {
84 coverPage.hide();
76 coverPage.hide();
85 }
77 }
86
78
87− function notifyAuthenticationSucceeded(showFakePassword) {
79+ function showFakePassword() {
88− if (showFakePassword) {
89− loginList.showFakePassword();
80+ loginList.showFakePassword();
90− }
91 }
81 }
92
82
93 function showLastChance() {
83 function showLastChance() {
@@ -125,8 +115,6 @@ FocusScope {
125 id: loginList
115 id: loginList
126 objectName: "loginList"
116 objectName: "loginList"
127
117
128− property int selectedUserIndex: 0
129−
130 width: units.gu(40)
118 width: units.gu(40)
131 anchors {
119 anchors {
132 left: parent.left
120 left: parent.left
@@ -141,15 +129,18 @@ FocusScope {
141 Behavior on boxVerticalOffset { UbuntuNumberAnimation {} }
129 Behavior on boxVerticalOffset { UbuntuNumberAnimation {} }
142
130
143 model: root.userModel
131 model: root.userModel
144− currentSession: LightDMService.users.data(selectedUserIndex, LightDMService.userRoles.SessionRole);
145 onResponded: root.responded(response)
132 onResponded: root.responded(response)
146− onSelected: {
147− root.selected(index)
133+ onSelected: root.selected(index)
148− loginList.selectedUserIndex = index;
149− }
150 onSessionChooserButtonClicked: parent.state = "SessionsList"
134 onSessionChooserButtonClicked: parent.state = "SessionsList"
135+ onCurrentIndexChanged: setCurrentSession()
151
136
152 Keys.forwardTo: [sessionChooserLoader.item]
137 Keys.forwardTo: [sessionChooserLoader.item]
138+
139+ Component.onCompleted: setCurrentSession()
140+
141+ function setCurrentSession() {
142+ currentSession = LightDMService.users.data(currentIndex, LightDMService.userRoles.SessionRole);
143+ }
153 }
144 }
154
145
155 Loader {
146 Loader {
@@ -177,7 +168,7 @@ FocusScope {
177 onSessionSelected: loginList.currentSession = sessionKey
168 onSessionSelected: loginList.currentSession = sessionKey
178 onShowLoginList: {
169 onShowLoginList: {
179 coverPage.state = "LoginList"
170 coverPage.state = "LoginList"
180− loginList.passwordInput.forceActiveFocus();
171+ loginList.tryToUnlock();
181 }
172 }
182 ignoreUnknownSignals: true
173 ignoreUnknownSignals: true
183 }
174 }
qml/Launcher/Drawer.qml+86−4
@@ -20,6 +20,7 @@ import Unity.Launcher 0.1
20import Utils 0.1
20import Utils 0.1
21import "../Components"
21import "../Components"
22import Qt.labs.settings 1.0
22import Qt.labs.settings 1.0
23+import GSettings 1.0
23
24
24FocusScope {
25FocusScope {
25 id: root
26 id: root
@@ -43,6 +44,16 @@ FocusScope {
43 searchField.focus = true;
44 searchField.focus = true;
44 }
45 }
45
46
47+ Keys.onPressed: {
48+ if (event.text.trim() !== "") {
49+ focusInput();
50+ searchField.text = event.text;
51+ }
52+ // Catch all presses here in case the navigation lets something through
53+ // We never want to end up in the launcher with focus
54+ event.accepted = true;
55+ }
56+
46 Settings {
57 Settings {
47 property alias selectedTab: sections.selectedIndex
58 property alias selectedTab: sections.selectedIndex
48 }
59 }
@@ -76,9 +87,13 @@ FocusScope {
76
87
77 TextField {
88 TextField {
78 id: searchField
89 id: searchField
90+ objectName: "searchField"
79 anchors { left: parent.left; top: parent.top; right: parent.right; margins: units.gu(1) }
91 anchors { left: parent.left; top: parent.top; right: parent.right; margins: units.gu(1) }
80 placeholderText: i18n.tr("Search…")
92 placeholderText: i18n.tr("Search…")
81 focus: true
93 focus: true
94+
95+ KeyNavigation.down: sections
96+
82 onAccepted: {
97 onAccepted: {
83 if (searchField.displayText != "" && listLoader.item && listLoader.item.currentItem) {
98 if (searchField.displayText != "" && listLoader.item && listLoader.item.currentItem) {
84 root.applicationSelected(listLoader.item.getFirstAppId());
99 root.applicationSelected(listLoader.item.getFirstAppId());
@@ -95,7 +110,14 @@ FocusScope {
95
110
96 Sections {
111 Sections {
97 id: sections
112 id: sections
113+ objectName: "drawerSections"
98 width: parent.width
114 width: parent.width
115+
116+ KeyNavigation.up: searchField
117+ KeyNavigation.down: headerFocusScope
118+ KeyNavigation.backtab: searchField
119+ KeyNavigation.tab: headerFocusScope
120+
99 actions: [
121 actions: [
100 Action {
122 Action {
101 text: i18n.ctr("Apps sorted alphabetically", "A-Z")
123 text: i18n.ctr("Apps sorted alphabetically", "A-Z")
@@ -115,9 +137,45 @@ FocusScope {
115 }
137 }
116 }
138 }
117
139
140+ FocusScope {
141+ id: headerFocusScope
142+ objectName: "headerFocusScope"
143+ KeyNavigation.up: sections
144+ KeyNavigation.down: listLoader.item
145+ KeyNavigation.backtab: sections
146+ KeyNavigation.tab: listLoader.item
147+ activeFocusOnTab: true
148+
149+ GSettings {
150+ id: settings
151+ schema.id: "com.canonical.Unity8"
152+ }
153+
154+ Keys.onPressed: {
155+ switch (event.key) {
156+ case Qt.Key_Return:
157+ case Qt.Key_Enter:
158+ case Qt.Key_Space:
159+ trigger();
160+ event.accepted = true;
161+ }
162+ }
163+
164+ function trigger() {
165+ Qt.openUrlExternally(settings.appstoreUri)
166+ }
167+ }
168+
118 Loader {
169 Loader {
119 id: listLoader
170 id: listLoader
171+ objectName: "drawerListLoader"
120− anchors { left: parent.left; top: sectionsContainer.bottom; right: parent.right; bottom: parent.bottom; leftMargin: units.gu(1); rightMargin: units.gu(1) }
172+ anchors { left: parent.left; top: sectionsContainer.bottom; right: parent.right; bottom: parent.bottom }
173+
174+ KeyNavigation.up: headerFocusScope
175+ KeyNavigation.down: searchField
176+ KeyNavigation.backtab: headerFocusScope
177+ KeyNavigation.tab: searchField
178+
121 sourceComponent: {
179 sourceComponent: {
122 switch (sections.selectedIndex) {
180 switch (sections.selectedIndex) {
123 case 0: return aToZComponent;
181 case 0: return aToZComponent;
@@ -167,10 +225,13 @@ FocusScope {
167 Component {
225 Component {
168 id: mostUsedComponent
226 id: mostUsedComponent
169 DrawerListView {
227 DrawerListView {
228+ id: mostUsedListView
170
229
171 header: MoreAppsHeader {
230 header: MoreAppsHeader {
172 width: parent.width
231 width: parent.width
173 height: units.gu(6)
232 height: units.gu(6)
233+ highlighted: headerFocusScope.activeFocus
234+ onClicked: headerFocusScope.trigger();
174 }
235 }
175
236
176 model: AppDrawerProxyModel {
237 model: AppDrawerProxyModel {
@@ -180,7 +241,8 @@ FocusScope {
180 }
241 }
181
242
182 delegate: UbuntuShape {
243 delegate: UbuntuShape {
183− width: parent.width
244+ width: parent.width - units.gu(2)
245+ anchors.horizontalCenter: parent.horizontalCenter
184 color: "#20ffffff"
246 color: "#20ffffff"
185 aspect: UbuntuShape.Flat
247 aspect: UbuntuShape.Flat
186 // NOTE: Cannot use gridView.rows here as it would evaluate to 0 at first and only update later,
248 // NOTE: Cannot use gridView.rows here as it would evaluate to 0 at first and only update later,
@@ -196,6 +258,9 @@ FocusScope {
196 bottomMargin: units.gu(1)
258 bottomMargin: units.gu(1)
197 clip: true
259 clip: true
198
260
261+ interactive: true
262+ focus: index == mostUsedListView.currentIndex
263+
199 model: sortProxyModel
264 model: sortProxyModel
200
265
201 delegateWidth: units.gu(8)
266 delegateWidth: units.gu(8)
@@ -209,10 +274,13 @@ FocusScope {
209 Component {
274 Component {
210 id: aToZComponent
275 id: aToZComponent
211 DrawerListView {
276 DrawerListView {
277+ id: aToZListView
212
278
213 header: MoreAppsHeader {
279 header: MoreAppsHeader {
214 width: parent.width
280 width: parent.width
215 height: units.gu(6)
281 height: units.gu(6)
282+ highlighted: headerFocusScope.activeFocus
283+ onClicked: headerFocusScope.trigger();
216 }
284 }
217
285
218 model: AppDrawerProxyModel {
286 model: AppDrawerProxyModel {
@@ -222,7 +290,8 @@ FocusScope {
222 }
290 }
223
291
224 delegate: UbuntuShape {
292 delegate: UbuntuShape {
225− width: parent.width
293+ width: parent.width - units.gu(2)
294+ anchors.horizontalCenter: parent.horizontalCenter
226 color: "#20ffffff"
295 color: "#20ffffff"
227 aspect: UbuntuShape.Flat
296 aspect: UbuntuShape.Flat
228
297
@@ -244,7 +313,8 @@ FocusScope {
244 anchors { left: parent.left; top: categoryNameLabel.bottom; right: parent.right; topMargin: units.gu(1) }
313 anchors { left: parent.left; top: categoryNameLabel.bottom; right: parent.right; topMargin: units.gu(1) }
245 height: rows * delegateHeight
314 height: rows * delegateHeight
246
315
247− interactive: false
316+ interactive: true
317+ focus: index == aToZListView.currentIndex
248
318
249 model: AppDrawerProxyModel {
319 model: AppDrawerProxyModel {
250 id: categoryModel
320 id: categoryModel
@@ -263,10 +333,13 @@ FocusScope {
263 Component {
333 Component {
264 id: drawerDelegateComponent
334 id: drawerDelegateComponent
265 AbstractButton {
335 AbstractButton {
336+ id: drawerDelegate
266 width: GridView.view.cellWidth
337 width: GridView.view.cellWidth
267 height: units.gu(10)
338 height: units.gu(10)
268 objectName: "drawerItem_" + model.appId
339 objectName: "drawerItem_" + model.appId
269
340
341+ readonly property bool focused: index === GridView.view.currentIndex && GridView.view.activeFocus
342+
270 onClicked: root.applicationSelected(model.appId)
343 onClicked: root.applicationSelected(model.appId)
271
344
272 Column {
345 Column {
@@ -290,6 +363,15 @@ FocusScope {
290 source: model.icon
363 source: model.icon
291 }
364 }
292 sourceFillMode: UbuntuShape.PreserveAspectCrop
365 sourceFillMode: UbuntuShape.PreserveAspectCrop
366+
367+ StyledItem {
368+ styleName: "FocusShape"
369+ anchors.fill: parent
370+ StyleHints {
371+ visible: drawerDelegate.focused
372+ radius: units.gu(2.55)
373+ }
374+ }
293 }
375 }
294
376
295 Label {
377 Label {
qml/Launcher/DrawerGridView.qml+3−1
@@ -17,7 +17,7 @@
17import QtQuick 2.4
17import QtQuick 2.4
18import "../Components"
18import "../Components"
19
19
20−Item {
20+FocusScope {
21 id: root
21 id: root
22
22
23 property int delegateWidth: units.gu(10)
23 property int delegateWidth: units.gu(10)
@@ -25,6 +25,7 @@ Item {
25 property alias delegate: gridView.delegate
25 property alias delegate: gridView.delegate
26 property alias model: gridView.model
26 property alias model: gridView.model
27 property alias interactive: gridView.interactive
27 property alias interactive: gridView.interactive
28+ property alias currentIndex: gridView.currentIndex
28
29
29 property alias header: gridView.header
30 property alias header: gridView.header
30 property alias topMargin: gridView.topMargin
31 property alias topMargin: gridView.topMargin
@@ -37,6 +38,7 @@ Item {
37 id: gridView
38 id: gridView
38 anchors.fill: parent
39 anchors.fill: parent
39 leftMargin: spacing
40 leftMargin: spacing
41+ focus: true
40
42
41 readonly property int overflow: width - (root.columns * root.delegateWidth)
43 readonly property int overflow: width - (root.columns * root.delegateWidth)
42 readonly property real spacing: overflow / (root.columns)
44 readonly property real spacing: overflow / (root.columns)
qml/Launcher/DrawerListView.qml+6
@@ -25,6 +25,12 @@ ListView {
25 bottomMargin: units.gu(1)
25 bottomMargin: units.gu(1)
26 spacing: units.gu(1)
26 spacing: units.gu(1)
27 clip: true
27 clip: true
28+ focus: true
29+
30+ onActiveFocusChanged: {
31+ currentIndex = -1;
32+ currentIndex = 0;
33+ }
28
34
29 function getFirstAppId() {
35 function getFirstAppId() {
30 return model.appId(0);
36 return model.appId(0);
qml/Launcher/Launcher.qml+11−8
@@ -74,7 +74,7 @@ FocusScope {
74 } else {
74 } else {
75 superPressTimer.stop();
75 superPressTimer.stop();
76 superLongPressTimer.stop();
76 superLongPressTimer.stop();
77− launcher.switchToNextState("");
77+ switchToNextState("");
78 panel.shortcutHintsShown = false;
78 panel.shortcutHintsShown = false;
79 }
79 }
80 }
80 }
@@ -87,14 +87,14 @@ FocusScope {
87 superPressTimer.stop();
87 superPressTimer.stop();
88 superLongPressTimer.stop();
88 superLongPressTimer.stop();
89 } else {
89 } else {
90+ switchToNextState("");
91+ root.focus = false;
90 if (panel.highlightIndex == -1) {
92 if (panel.highlightIndex == -1) {
91− showDashHome();
93+ root.showDashHome();
92 } else if (panel.highlightIndex >= 0){
94 } else if (panel.highlightIndex >= 0){
93 launcherApplicationSelected(LauncherModel.get(panel.highlightIndex).appId);
95 launcherApplicationSelected(LauncherModel.get(panel.highlightIndex).appId);
94 }
96 }
95 panel.highlightIndex = -2;
97 panel.highlightIndex = -2;
96− switchToNextState("");
97− root.focus = false;
98 }
98 }
99 }
99 }
100
100
@@ -108,6 +108,10 @@ FocusScope {
108 }
108 }
109 }
109 }
110
110
111+ onPanelWidthChanged: {
112+ hint();
113+ }
114+
111 function hide(flags) {
115 function hide(flags) {
112 if ((flags & ignoreHideIfMouseOverLauncher) && Utils.Functions.itemUnderMouse(panel)) {
116 if ((flags & ignoreHideIfMouseOverLauncher) && Utils.Functions.itemUnderMouse(panel)) {
113 return;
117 return;
@@ -206,7 +210,7 @@ FocusScope {
206 case Qt.Key_Return:
210 case Qt.Key_Return:
207 case Qt.Key_Space:
211 case Qt.Key_Space:
208 if (panel.highlightIndex == -1) {
212 if (panel.highlightIndex == -1) {
209− showDashHome();
213+ root.showDashHome();
210 } else if (panel.highlightIndex >= 0) {
214 } else if (panel.highlightIndex >= 0) {
211 launcherApplicationSelected(LauncherModel.get(panel.highlightIndex).appId);
215 launcherApplicationSelected(LauncherModel.get(panel.highlightIndex).appId);
212 }
216 }
@@ -469,7 +473,7 @@ FocusScope {
469 }
473 }
470 onPassed: {
474 onPassed: {
471 if (root.drawerEnabled) {
475 if (root.drawerEnabled) {
472− root.switchToNextState("drawer");
476+ root.openDrawer()
473 }
477 }
474 }
478 }
475
479
@@ -545,8 +549,7 @@ FocusScope {
545 if (!dragging) {
549 if (!dragging) {
546 if (distance > panel.width / 2) {
550 if (distance > panel.width / 2) {
547 if (root.drawerEnabled && distance > panel.width * 3 && dragDirection() !== "left") {
551 if (root.drawerEnabled && distance > panel.width * 3 && dragDirection() !== "left") {
548− root.switchToNextState("drawer");
552+ root.openDrawer(false)
549− root.focus = true;
550 } else {
553 } else {
551 root.switchToNextState("visible");
554 root.switchToNextState("visible");
552 }
555 }
qml/Launcher/LauncherDelegate.qml+8−9
@@ -124,15 +124,14 @@ Item {
124 height: parent.itemHeight + units.gu(1)
124 height: parent.itemHeight + units.gu(1)
125 anchors.centerIn: parent
125 anchors.centerIn: parent
126
126
127− Image {
127+ StyledItem {
128− objectName: "focusRing"
128+ styleName: "FocusShape"
129− anchors.centerIn: iconShape
129+ anchors.fill: iconShape
130− height: width * 15 / 16
130+ activeFocusOnTab: true
131− width: iconShape.width + units.gu(1)
131+ StyleHints {
132− source: "graphics/launcher-app-focus-ring.svg"
132+ visible: root.highlighted
133− sourceSize.width: width
133+ radius: units.gu(2.55)
134− sourceSize.height: height
134+ }
135− visible: root.highlighted
136 }
135 }
137
136
138 ProportionalShape {
137 ProportionalShape {
qml/Launcher/LauncherPanel.qml+11−6
@@ -80,6 +80,7 @@ Rectangle {
80 }
80 }
81
81
82 Rectangle {
82 Rectangle {
83+ id: bfb
83 objectName: "buttonShowDashHome"
84 objectName: "buttonShowDashHome"
84 width: parent.width
85 width: parent.width
85 height: width * .9
86 height: width * .9
@@ -100,13 +101,15 @@ Rectangle {
100 activeFocusOnPress: false
101 activeFocusOnPress: false
101 onClicked: root.showDashHome()
102 onClicked: root.showDashHome()
102 }
103 }
103− Rectangle {
104+
104− objectName: "bfbFocusHighlight"
105+ StyledItem {
106+ styleName: "FocusShape"
105 anchors.fill: parent
107 anchors.fill: parent
106− border.color: "white"
108+ anchors.margins: units.gu(.5)
107− border.width: units.dp(1)
109+ StyleHints {
108− color: "transparent"
109− visible: parent.highlighted
110+ visible: bfb.highlighted
111+ radius: 0
112+ }
110 }
113 }
111 }
114 }
112
115
@@ -788,6 +791,8 @@ Rectangle {
788 quickList.model = launcherListView.model.get(index).quickList;
791 quickList.model = launcherListView.model.get(index).quickList;
789 quickList.appId = launcherListView.model.get(index).appId;
792 quickList.appId = launcherListView.model.get(index).appId;
790 quickList.state = "open";
793 quickList.state = "open";
794+ root.highlightIndex = index;
795+ quickList.forceActiveFocus();
791 }
796 }
792
797
793 Item {
798 Item {
qml/Launcher/MoreAppsHeader.qml+12−5
@@ -4,17 +4,24 @@ import Ubuntu.Components 1.3
4AbstractButton {
4AbstractButton {
5 id: root
5 id: root
6
6
7− onClicked: {
7+ property bool highlighted: false
8− // TODO: Make this point to the snappy store as soon as we stop landing to vivid
9− Qt.openUrlExternally("scope://com.canonical.scopes.clickstore")
10− }
11
8
12 UbuntuShape {
9 UbuntuShape {
13− width: parent.width
10+ width: parent.width - units.gu(2)
11+ anchors.horizontalCenter: parent.horizontalCenter
14 height: parent.height - units.gu(1)
12 height: parent.height - units.gu(1)
15 color: "#20ffffff"
13 color: "#20ffffff"
16 aspect: UbuntuShape.Flat
14 aspect: UbuntuShape.Flat
17
15
16+ StyledItem {
17+ styleName: "FocusShape"
18+ anchors.fill: parent
19+ activeFocusOnTab: true
20+ StyleHints {
21+ visible: root.highlighted
22+ }
23+ }
24+
18 Row {
25 Row {
19 anchors.fill: parent
26 anchors.fill: parent
20 anchors.margins: units.gu(1)
27 anchors.margins: units.gu(1)
qml/Launcher/graphics/launcher-app-focus-ring.svgdeleted−12
@@ -1,12 +0,0 @@
1−<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2−<svg width="172px" height="163px" viewBox="0 0 172 163" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
3− <!-- Generator: Sketch 3.4.4 (17249) - http://www.bohemiancoding.com/sketch -->
4− <title>Shape</title>
5− <desc>Created with Sketch.</desc>
6− <defs></defs>
7− <g id="•-Launcher" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
8− <g id="Artboard-9" sketch:type="MSArtboardGroup" transform="translate(-163.000000, -1436.000000)" fill="#E95420">
9− <path d="M221.983432,1440 L221.983432,1440 C195.6127,1440 184.708233,1442.4723 177.107949,1450.10734 C169.476819,1457.77336 167,1468.79245 167,1495.3481 L167,1538.9019 C167,1565.45755 169.476819,1576.47664 177.107949,1584.14266 C184.708233,1591.7777 195.6127,1594.25 221.983432,1594.25 L276.016868,1594.25 C302.387595,1594.25 313.291998,1591.77771 320.892221,1584.14264 C328.523252,1576.47663 331,1565.45769 331,1538.9019 L331,1495.3481 C331,1468.79231 328.523252,1457.77337 320.892221,1450.10736 C313.291998,1442.47229 302.387595,1440 276.016868,1440 L221.983432,1440 Z M221.983432,1436 L276.016868,1436 C302.345315,1436 314.848953,1438.36655 323.727108,1447.2854 C332.633306,1456.23243 335,1468.85167 335,1495.3481 L335,1538.9019 C335,1565.39833 332.633306,1578.01757 323.727108,1586.9646 C314.848953,1595.88345 302.345315,1598.25 276.016868,1598.25 L221.983432,1598.25 C195.654985,1598.25 183.151291,1595.88345 174.273077,1586.96463 C165.366772,1578.0176 163,1565.39822 163,1538.9019 L163,1495.3481 C163,1468.85178 165.366772,1456.2324 174.273077,1447.28537 C183.151291,1438.36655 195.654985,1436 221.983432,1436 L221.983432,1436 Z" id="Shape" sketch:type="MSShapeGroup"></path>
10− </g>
11− </g>
12−</svg>
qml/OrientedShell.qml+2−1
@@ -266,7 +266,8 @@ Item {
266 nativeWidth: root.width
266 nativeWidth: root.width
267 nativeHeight: root.height
267 nativeHeight: root.height
268 mode: applicationArguments.mode
268 mode: applicationArguments.mode
269− hasMouse: miceModel.count + touchPadModel.count > 0
269+ hasMouse: pointerInputDevices > 0
270+ hasKeyboard: keyboardsModel.count > 0
270 // TODO: Factor in if the current screen is a touch screen and if the user wants to
271 // TODO: Factor in if the current screen is a touch screen and if the user wants to
271 // have multiple keyboards around. For now we only enable one keyboard at a time
272 // have multiple keyboards around. For now we only enable one keyboard at a time
272 // thus hiding it here if there is a physical one around or if we have a second
273 // thus hiding it here if there is a physical one around or if we have a second
qml/Panel/PanelBar.qml+1
@@ -49,6 +49,7 @@ Item {
49 row.resetCurrentItem();
49 row.resetCurrentItem();
50 }
50 }
51 row.setCurrentItemIndex(index);
51 row.setCurrentItemIndex(index);
52+ d.alignIndicators();
52 }
53 }
53
54
54 function addScrollOffset(scrollAmmout) {
55 function addScrollOffset(scrollAmmout) {
qml/Panel/PanelMenu.qml+14
@@ -140,6 +140,19 @@ Showable {
140 visible: !root.fullyClosed
140 visible: !root.fullyClosed
141 }
141 }
142
142
143+ Keys.onPressed: {
144+ if (event.key === Qt.Key_Left) {
145+ bar.setCurrentItemIndex(bar.currentItemIndex - 1);
146+ event.accepted = true;
147+ } else if (event.key === Qt.Key_Right) {
148+ bar.setCurrentItemIndex(bar.currentItemIndex + 1);
149+ event.accepted = true;
150+ } else if (event.key === Qt.Key_Escape) {
151+ root.hide();
152+ event.accepted = true;
153+ }
154+ }
155+
143 PanelBar {
156 PanelBar {
144 id: bar
157 id: bar
145 objectName: "indicatorsBar"
158 objectName: "indicatorsBar"
@@ -368,6 +381,7 @@ Showable {
368 State {
381 State {
369 name: "commit"
382 name: "commit"
370 extend: "locked"
383 extend: "locked"
384+ PropertyChanges { target: root; focus: true }
371 PropertyChanges { target: bar; interactive: true }
385 PropertyChanges { target: bar; interactive: true }
372 PropertyChanges {
386 PropertyChanges {
373 target: d;
387 target: d;
qml/Shell.qml+49−5
@@ -68,6 +68,7 @@ StyledItem {
68 stage.updateFocusedAppOrientationAnimated();
68 stage.updateFocusedAppOrientationAnimated();
69 }
69 }
70 property bool hasMouse: false
70 property bool hasMouse: false
71+ property bool hasKeyboard: false
71
72
72 // to be read from outside
73 // to be read from outside
73 readonly property int mainAppWindowOrientationAngle: stage.mainAppWindowOrientationAngle
74 readonly property int mainAppWindowOrientationAngle: stage.mainAppWindowOrientationAngle
@@ -240,7 +241,7 @@ StyledItem {
240 // Ignore when greeter is active, to avoid pocket presses
241 // Ignore when greeter is active, to avoid pocket presses
241 if (!greeter.active) {
242 if (!greeter.active) {
242 launcher.fadeOut();
243 launcher.fadeOut();
243− shell.showHome();
244+ ApplicationManager.requestFocusApplication("unity8-dash");
244 }
245 }
245 }
246 }
246 onTouchBegun: { cursor.opacity = 0; }
247 onTouchBegun: { cursor.opacity = 0; }
@@ -319,6 +320,35 @@ StyledItem {
319 panel.applicationMenus.hide();
320 panel.applicationMenus.hide();
320 }
321 }
321 }
322 }
323+
324+ TouchGestureArea {
325+ anchors.fill: stage
326+
327+ minimumTouchPoints: 4
328+ maximumTouchPoints: minimumTouchPoints
329+
330+ readonly property bool recognisedPress: status == TouchGestureArea.Recognized &&
331+ touchPoints.length >= minimumTouchPoints &&
332+ touchPoints.length <= maximumTouchPoints
333+ property bool wasPressed: false
334+
335+ onRecognisedPressChanged: {
336+ if (recognisedPress) {
337+ wasPressed = true;
338+ }
339+ }
340+
341+ onStatusChanged: {
342+ if (status !== TouchGestureArea.Recognized) {
343+ if (status === TouchGestureArea.WaitingForTouch) {
344+ if (wasPressed && !dragging) {
345+ launcher.openDrawer(true);
346+ }
347+ }
348+ wasPressed = false;
349+ }
350+ }
351+ }
322 }
352 }
323
353
324 InputMethod {
354 InputMethod {
@@ -343,6 +373,16 @@ StyledItem {
343 onLoaded: {
373 onLoaded: {
344 item.objectName = "greeter"
374 item.objectName = "greeter"
345 }
375 }
376+ property bool openDrawerAfterUnlock: false
377+ Connections {
378+ target: greeter
379+ onActiveChanged: {
380+ if (!greeter.active && greeterLoader.openDrawerAfterUnlock) {
381+ launcher.openDrawer(false);
382+ greeterLoader.openDrawerAfterUnlock = false;
383+ }
384+ }
385+ }
346 }
386 }
347
387
348 Component {
388 Component {
@@ -434,9 +474,11 @@ StyledItem {
434 if (shell.mode === "greeter") {
474 if (shell.mode === "greeter") {
435 SessionBroadcast.requestHomeShown(AccountsService.user);
475 SessionBroadcast.requestHomeShown(AccountsService.user);
436 } else {
476 } else {
437− var animate = !LightDMService.greeter.active && !stages.shown;
477+ if (!greeter.active) {
438− dash.setCurrentScope(0, animate, false);
478+ launcher.openDrawer(false);
439− ApplicationManager.requestFocusApplication("unity8-dash");
479+ } else {
480+ greeterLoader.openDrawerAfterUnlock = true;
481+ }
440 }
482 }
441 }
483 }
442
484
@@ -512,7 +554,7 @@ StyledItem {
512 lockedVisible: shell.usageScenario == "desktop" && !settings.autohideLauncher && !panel.fullscreenMode
554 lockedVisible: shell.usageScenario == "desktop" && !settings.autohideLauncher && !panel.fullscreenMode
513 blurSource: greeter.shown ? greeter : stages
555 blurSource: greeter.shown ? greeter : stages
514 topPanelHeight: panel.panelHeight
556 topPanelHeight: panel.panelHeight
515− drawerEnabled: !greeter.shown
557+ drawerEnabled: !greeter.active
516
558
517 onShowDashHome: showHome()
559 onShowDashHome: showHome()
518 onLauncherApplicationSelected: {
560 onLauncherApplicationSelected: {
@@ -657,8 +699,10 @@ StyledItem {
657 id: dialogs
699 id: dialogs
658 objectName: "dialogs"
700 objectName: "dialogs"
659 anchors.fill: parent
701 anchors.fill: parent
702+ visible: hasActiveDialog
660 z: overlay.z + 10
703 z: overlay.z + 10
661 usageScenario: shell.usageScenario
704 usageScenario: shell.usageScenario
705+ hasKeyboard: shell.hasKeyboard
662 onPowerOffClicked: {
706 onPowerOffClicked: {
663 shutdownFadeOutRectangle.enabled = true;
707 shutdownFadeOutRectangle.enabled = true;
664 shutdownFadeOutRectangle.visible = true;
708 shutdownFadeOutRectangle.visible = true;
qml/Stage/DecoratedWindow.qml+36−43
@@ -196,18 +196,19 @@ FocusScope {
196 ]
196 ]
197 }
197 }
198
198
199− MouseArea {
199+ WindowDecoration {
200+ id: decoration
201+ closeButtonVisible: root.application.appId !== "unity8-dash"
202+ objectName: "appWindowDecoration"
203+
200 anchors { left: parent.left; top: parent.top; right: parent.right }
204 anchors { left: parent.left; top: parent.top; right: parent.right }
201 height: units.gu(3)
205 height: units.gu(3)
202
206
203− opacity: root.hasDecoration ? Math.min(1, root.showDecoration) : 0
207+ title: applicationWindow.title
204
208
209+ opacity: root.hasDecoration ? Math.min(1, root.showDecoration) : 0
205 Behavior on opacity { UbuntuNumberAnimation { } }
210 Behavior on opacity { UbuntuNumberAnimation { } }
206
211
207− drag.target: Item {}
208− drag.filterChildren: true
209− drag.threshold: 0
210−
211 onPressed: root.decorationPressed();
212 onPressed: root.decorationPressed();
212 onPressedChanged: moveHandler.handlePressedChanged(pressed, pressedButtons, mouseX, mouseY)
213 onPressedChanged: moveHandler.handlePressedChanged(pressed, pressedButtons, mouseX, mouseY)
213 onPositionChanged: moveHandler.handlePositionChanged(mouse)
214 onPositionChanged: moveHandler.handlePositionChanged(mouse)
@@ -216,47 +217,39 @@ FocusScope {
216 moveHandler.handleReleased();
217 moveHandler.handleReleased();
217 }
218 }
218
219
219− WindowDecoration {
220− id: decoration
221− closeButtonVisible: root.application.appId !== "unity8-dash"
222− objectName: "appWindowDecoration"
223− anchors.fill: parent
224− title: applicationWindow.title
225−
226− onCloseClicked: root.closeClicked();
220+ onCloseClicked: root.closeClicked();
227− onMaximizeClicked: { root.decorationPressed(); root.maximizeClicked(); }
221+ onMaximizeClicked: { root.decorationPressed(); root.maximizeClicked(); }
228− onMaximizeHorizontallyClicked: { root.decorationPressed(); root.maximizeHorizontallyClicked(); }
222+ onMaximizeHorizontallyClicked: { root.decorationPressed(); root.maximizeHorizontallyClicked(); }
229− onMaximizeVerticallyClicked: { root.decorationPressed(); root.maximizeVerticallyClicked(); }
223+ onMaximizeVerticallyClicked: { root.decorationPressed(); root.maximizeVerticallyClicked(); }
230− onMinimizeClicked: root.minimizeClicked();
224+ onMinimizeClicked: root.minimizeClicked();
231−
232− enableMenus: {
233− return active &&
234− surface &&
235− (PanelState.focusedPersistentSurfaceId === surface.persistentId && !PanelState.decorationsVisible)
236− }
237− menu: sharedAppModel.model
238
225
239− Indicators.SharedUnityMenuModel {
226+ enableMenus: {
240− id: sharedAppModel
227+ return active &&
241− property var menus: surface ? ApplicationMenuRegistry.getMenusForSurface(surface.persistentId) : []
228+ surface &&
242− property var menuService: menus.length > 0 ? menus[0] : undefined
229+ (PanelState.focusedPersistentSurfaceId === surface.persistentId && !PanelState.decorationsVisible)
230+ }
231+ menu: sharedAppModel.model
243
232
244− busName: menuService ? menuService.service : ""
233+ Indicators.SharedUnityMenuModel {
245− menuObjectPath: menuService && menuService.menuPath ? menuService.menuPath : ""
234+ id: sharedAppModel
246− actions: menuService && menuService.actionPath ? { "unity": menuService.actionPath } : {}
235+ property var menus: surface ? ApplicationMenuRegistry.getMenusForSurface(surface.persistentId) : []
247− }
236+ property var menuService: menus.length > 0 ? menus[0] : undefined
248
237
238+ busName: menuService ? menuService.service : ""
239+ menuObjectPath: menuService && menuService.menuPath ? menuService.menuPath : ""
240+ actions: menuService && menuService.actionPath ? { "unity": menuService.actionPath } : {}
241+ }
242+
249− Connections {
243+ Connections {
250− target: ApplicationMenuRegistry
244+ target: ApplicationMenuRegistry
251− onSurfaceMenuRegistered: {
245+ onSurfaceMenuRegistered: {
252− if (surface && surfaceId === surface.persistentId) {
246+ if (surface && surfaceId === surface.persistentId) {
253− sharedAppModel.menus = Qt.binding(function() { return surface ? ApplicationMenuRegistry.getMenusForSurface(surface.persistentId) : [] });
247+ sharedAppModel.menus = Qt.binding(function() { return surface ? ApplicationMenuRegistry.getMenusForSurface(surface.persistentId) : [] });
254− }
255 }
248 }
256− onSurfaceMenuUnregistered: {
249+ }
250+ onSurfaceMenuUnregistered: {
257− if (surface && surfaceId === surface.persistentId) {
251+ if (surface && surfaceId === surface.persistentId) {
258− sharedAppModel.menus = Qt.binding(function() { return surface ? ApplicationMenuRegistry.getMenusForSurface(surface.persistentId) : [] });
252+ sharedAppModel.menus = Qt.binding(function() { return surface ? ApplicationMenuRegistry.getMenusForSurface(surface.persistentId) : [] });
259− }
260 }
253 }
261 }
254 }
262 }
255 }
qml/Stage/WindowDecoration.qml+6−2
@@ -38,6 +38,10 @@ MouseArea {
38 acceptedButtons: Qt.AllButtons // prevent leaking unhandled mouse events
38 acceptedButtons: Qt.AllButtons // prevent leaking unhandled mouse events
39 hoverEnabled: true
39 hoverEnabled: true
40
40
41+ drag.target: Item {}
42+ drag.filterChildren: true
43+ drag.threshold: 0
44+
41 signal closeClicked()
45 signal closeClicked()
42 signal minimizeClicked()
46 signal minimizeClicked()
43 signal maximizeClicked()
47 signal maximizeClicked()
@@ -64,8 +68,8 @@ MouseArea {
64 (menuBar.showRequested || root.containsMouse)
68 (menuBar.showRequested || root.containsMouse)
65 }
69 }
66
70
67− // We dont want touch events to fall through to parent,
71+ // We dont want touch events to fall through to parent as it expect some child MouseArea to have them
68− // otherwise the containsMouse will not work.
72+ // If not some MouseArea in the menu bar, it will be this one.
69 MouseArea {
73 MouseArea {
70 anchors.fill: parent
74 anchors.fill: parent
71 propagateComposedEvents: true
75 propagateComposedEvents: true
src/MouseTouchAdaptor.cpp+28−1
@@ -72,6 +72,7 @@ namespace {
72MouseTouchAdaptor *g_instance = nullptr;
72MouseTouchAdaptor *g_instance = nullptr;
73
73
74const Qt::KeyboardModifiers TRI_PRESS_MODIFIER = Qt::ShiftModifier|Qt::ControlModifier|Qt::AltModifier;
74const Qt::KeyboardModifiers TRI_PRESS_MODIFIER = Qt::ShiftModifier|Qt::ControlModifier|Qt::AltModifier;
75+const Qt::KeyboardModifiers QUAD_PRESS_MODIFIER = TRI_PRESS_MODIFIER|Qt::MetaModifier;
75
76
76Qt::MouseButton translateMouseButton(xcb_button_t detail)
77Qt::MouseButton translateMouseButton(xcb_button_t detail)
77{
78{
@@ -91,7 +92,7 @@ Qt::KeyboardModifiers translateMofidier(uint32_t mod)
91 if (mod & 0x01) qtMod |= Qt::ShiftModifier;
92 if (mod & 0x01) qtMod |= Qt::ShiftModifier;
92 if (mod & 0x04) qtMod |= Qt::ControlModifier;
93 if (mod & 0x04) qtMod |= Qt::ControlModifier;
93 if (mod & 0x08) qtMod |= Qt::AltModifier;
94 if (mod & 0x08) qtMod |= Qt::AltModifier;
94− if (mod & 0x80) qtMod |= Qt::MetaModifier;
95+ if (mod & 0x40) qtMod |= Qt::MetaModifier;
95
96
96 return qtMod;
97 return qtMod;
97}
98}
@@ -101,6 +102,7 @@ MouseTouchAdaptor::MouseTouchAdaptor()
101 : QObject(nullptr)
102 : QObject(nullptr)
102 , m_leftButtonIsPressed(false)
103 , m_leftButtonIsPressed(false)
103 , m_triPressModifier(false)
104 , m_triPressModifier(false)
105+ , m_quadPressModifier(false)
104 , m_enabled(true)
106 , m_enabled(true)
105{
107{
106 QCoreApplication::instance()->installNativeEventFilter(this);
108 QCoreApplication::instance()->installNativeEventFilter(this);
@@ -297,6 +299,13 @@ bool MouseTouchAdaptor::handleButtonPress(WId windowId, uint32_t detail, uint32_
297 touchEvent.press(2, windowPos);
299 touchEvent.press(2, windowPos);
298 m_triPressModifier = true;
300 m_triPressModifier = true;
299 }
301 }
302+ if (qtMod == QUAD_PRESS_MODIFIER) {
303+ touchEvent.press(1, windowPos);
304+ touchEvent.press(2, windowPos);
305+ touchEvent.press(3, windowPos);
306+ m_quadPressModifier = true;
307+ }
308+
300 touchEvent.commit(false /* processEvents */);
309 touchEvent.commit(false /* processEvents */);
301
310
302 m_leftButtonIsPressed = true;
311 m_leftButtonIsPressed = true;
@@ -322,10 +331,16 @@ bool MouseTouchAdaptor::handleButtonRelease(WId windowId, uint32_t detail, uint3
322 touchEvent.release(1, windowPos);
331 touchEvent.release(1, windowPos);
323 touchEvent.release(2, windowPos);
332 touchEvent.release(2, windowPos);
324 }
333 }
334+ if (m_quadPressModifier) {
335+ touchEvent.release(1, windowPos);
336+ touchEvent.release(2, windowPos);
337+ touchEvent.release(3, windowPos);
338+ }
325 touchEvent.commit(false /* processEvents */);
339 touchEvent.commit(false /* processEvents */);
326
340
327 m_leftButtonIsPressed = false;
341 m_leftButtonIsPressed = false;
328 m_triPressModifier = false;
342 m_triPressModifier = false;
343+ m_quadPressModifier = false;
329 return true;
344 return true;
330}
345}
331
346
@@ -354,6 +369,18 @@ bool MouseTouchAdaptor::handleMotionNotify(WId windowId, uint32_t modifiers, int
354 m_triPressModifier = false;
369 m_triPressModifier = false;
355 }
370 }
356 }
371 }
372+ if (m_quadPressModifier) {
373+ if (qtMod == QUAD_PRESS_MODIFIER) {
374+ touchEvent.move(1, windowPos);
375+ touchEvent.move(2, windowPos);
376+ touchEvent.move(3, windowPos);
377+ } else {
378+ touchEvent.release(1, windowPos);
379+ touchEvent.release(2, windowPos);
380+ touchEvent.release(3, windowPos);
381+ m_quadPressModifier = false;
382+ }
383+ }
357 touchEvent.commit(false /* processEvents */);
384 touchEvent.commit(false /* processEvents */);
358
385
359 return true;
386 return true;
src/MouseTouchAdaptor.h+1
@@ -59,6 +59,7 @@ private:
59 QTouchDevice *m_touchDevice;
59 QTouchDevice *m_touchDevice;
60 bool m_leftButtonIsPressed;
60 bool m_leftButtonIsPressed;
61 bool m_triPressModifier;
61 bool m_triPressModifier;
62+ bool m_quadPressModifier;
62
63
63
64
64 bool m_enabled;
65 bool m_enabled;
tests/CMakeLists.txt+5−3
@@ -7,8 +7,10 @@ add_meta_test(unittests)
7add_meta_test(uitests)
7add_meta_test(uitests)
8add_meta_test(xvfbuitests)
8add_meta_test(xvfbuitests)
9
9
10+# Run our meta-meta tests serially because we don't need to nest
11+# parallelized tests.
10−add_meta_test(alltests DEPENDS unittests uitests)
12+add_meta_test(alltests SERIAL DEPENDS unittests uitests)
11−add_meta_test(xvfballtests DEPENDS unittests xvfbuitests)
13+add_meta_test(xvfballtests SERIAL DEPENDS unittests xvfbuitests)
12
14
13# Support libraries and plugins
15# Support libraries and plugins
14add_subdirectory(mocks)
16add_subdirectory(mocks)
@@ -42,8 +44,8 @@ macro(unity8_parse_arguments)
42
44
43 set(ld_paths)
45 set(ld_paths)
44 list(APPEND ld_paths
46 list(APPEND ld_paths
47+ ${UNITY_MOCKPATH}/liblightdm
45 ${UNITY_MOCKPATH}/libusermetrics
48 ${UNITY_MOCKPATH}/libusermetrics
46− ${UNITY_MOCKPATH}/LightDM/IntegratedLightDM/liblightdm
47 )
49 )
48
50
49 string(REPLACE ";" ":" ld_library_path "${ld_paths}")
51 string(REPLACE ";" ":" ld_library_path "${ld_paths}")
tests/autopilot/unity8/fixture_setup.py+1−1
@@ -74,7 +74,7 @@ class LaunchUnityWithFakeSensors(fixtures.Fixture):
74 def _get_lightdm_mock_path(self):
74 def _get_lightdm_mock_path(self):
75 lib_path = get_mocks_library_path()
75 lib_path = get_mocks_library_path()
76 lightdm_mock_path = os.path.abspath(
76 lightdm_mock_path = os.path.abspath(
77− os.path.join(lib_path, "LightDM" ,"IntegratedLightDM", "liblightdm")
77+ os.path.join(lib_path, "liblightdm")
78 )
78 )
79
79
80 if not os.path.exists(lightdm_mock_path):
80 if not os.path.exists(lightdm_mock_path):
tests/autopilot/unity8/shell/tests/__init__.py+1−1
@@ -264,7 +264,7 @@ class UnityTestCase(AutopilotTestCase):
264 def _get_lightdm_mock_path(self):
264 def _get_lightdm_mock_path(self):
265 lib_path = get_mocks_library_path()
265 lib_path = get_mocks_library_path()
266 lightdm_mock_path = os.path.abspath(
266 lightdm_mock_path = os.path.abspath(
267− os.path.join(lib_path, "LightDM" ,"IntegratedLightDM", "liblightdm")
267+ os.path.join(lib_path, "liblightdm")
268 )
268 )
269
269
270 if not os.path.exists(lightdm_mock_path):
270 if not os.path.exists(lightdm_mock_path):
tests/mocks/AccountsService/AccountsService.cpp+2−3
@@ -15,7 +15,7 @@
15 */
15 */
16
16
17#include "AccountsService.h"
17#include "AccountsService.h"
18−#include "MockUsersModel.h"
18+#include "UsersModel.h"
19
19
20#include <QLightDM/UsersModel>
20#include <QLightDM/UsersModel>
21#include <paths.h>
21#include <paths.h>
@@ -33,9 +33,8 @@ AccountsService::AccountsService(QObject* parent)
33 m_demoEdgesCompleted(),
33 m_demoEdgesCompleted(),
34 m_hereEnabled(false),
34 m_hereEnabled(false),
35 m_hereLicensePath(""),
35 m_hereLicensePath(""),
36− m_usersModel(new MockUsersModel(this))
36+ m_usersModel(new UsersModel(this))
37{
37{
38− m_usersModel->setMockMode("full");
39}
38}
40
39
41QString AccountsService::user() const
40QString AccountsService::user() const
tests/mocks/AccountsService/AccountsService.h+3−6
@@ -14,15 +14,14 @@
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 */
16
16
17−#ifndef UNITY_MOCK_ACCOUNTSSERVICE_H
17+#pragma once
18−#define UNITY_MOCK_ACCOUNTSSERVICE_H
19
18
20#include <QObject>
19#include <QObject>
21#include <QString>
20#include <QString>
22#include <QStringList>
21#include <QStringList>
23#include <QVariant>
22#include <QVariant>
24
23
25−class MockUsersModel;
24+class UsersModel;
26
25
27class AccountsService: public QObject
26class AccountsService: public QObject
28{
27{
@@ -165,7 +164,5 @@ private:
165 QString m_realName;
164 QString m_realName;
166 QStringList m_kbdMap;
165 QStringList m_kbdMap;
167 QString m_email;
166 QString m_email;
168− MockUsersModel *m_usersModel;
167+ UsersModel *m_usersModel;
169};
168};
170−
171−#endif
tests/mocks/AccountsService/CMakeLists.txt+3−2
@@ -1,14 +1,15 @@
1include_directories(
1include_directories(
2 ${CMAKE_CURRENT_BINARY_DIR}
2 ${CMAKE_CURRENT_BINARY_DIR}
3 ${CMAKE_SOURCE_DIR}/plugins/LightDM
3 ${CMAKE_SOURCE_DIR}/plugins/LightDM
4+ ${CMAKE_SOURCE_DIR}/plugins/LightDM/IntegratedLightDM
4 ${CMAKE_SOURCE_DIR}/plugins/Utils
5 ${CMAKE_SOURCE_DIR}/plugins/Utils
5− ${CMAKE_SOURCE_DIR}/tests/mocks/LightDM/IntegratedLightDM
6 )
6 )
7
7
8add_library(MockAccountsService-qml MODULE
8add_library(MockAccountsService-qml MODULE
9+ ${CMAKE_SOURCE_DIR}/plugins/LightDM/Greeter.cpp
10+ ${CMAKE_SOURCE_DIR}/plugins/LightDM/PromptsModel.cpp
9 ${CMAKE_SOURCE_DIR}/plugins/LightDM/UsersModel.cpp
11 ${CMAKE_SOURCE_DIR}/plugins/LightDM/UsersModel.cpp
10 ${CMAKE_SOURCE_DIR}/plugins/Utils/unitysortfilterproxymodelqml.cpp
12 ${CMAKE_SOURCE_DIR}/plugins/Utils/unitysortfilterproxymodelqml.cpp
11− ${CMAKE_SOURCE_DIR}/tests/mocks/LightDM/IntegratedLightDM/MockUsersModel.cpp
12 AccountsService.cpp
13 AccountsService.cpp
13 plugin.cpp
14 plugin.cpp
14 )
15 )
tests/mocks/CMakeLists.txt+2−1
@@ -32,8 +32,9 @@ add_subdirectory(Biometryd)
32add_subdirectory(Cursor)
32add_subdirectory(Cursor)
33add_subdirectory(GSettings.1.0)
33add_subdirectory(GSettings.1.0)
34add_subdirectory(indicator-service)
34add_subdirectory(indicator-service)
35+add_subdirectory(liblightdm)
35add_subdirectory(libusermetrics)
36add_subdirectory(libusermetrics)
36−add_subdirectory(LightDM)
37+add_subdirectory(LightDMController)
37add_subdirectory(Lights)
38add_subdirectory(Lights)
38add_subdirectory(MeeGo)
39add_subdirectory(MeeGo)
39add_subdirectory(Powerd)
40add_subdirectory(Powerd)
tests/mocks/GSettings.1.0/fake_gsettings.cpp+14
@@ -29,6 +29,7 @@ GSettingsControllerQml::GSettingsControllerQml()
29 , m_edgeDragWidth(2)
29 , m_edgeDragWidth(2)
30 , m_enableLauncher(true)
30 , m_enableLauncher(true)
31 , m_enableIndicatorMenu(true)
31 , m_enableIndicatorMenu(true)
32+ , m_appstoreUri("http://uappexplorer.com")
32{
33{
33}
34}
34
35
@@ -173,6 +174,11 @@ void GSettingsControllerQml::setEnableIndicatorMenu(bool enableIndicatorMenu)
173 }
174 }
174}
175}
175
176
177+QString GSettingsControllerQml::appstoreUri() const
178+{
179+ return m_appstoreUri;
180+}
181+
176GSettingsSchemaQml::GSettingsSchemaQml(QObject *parent): QObject(parent) {
182GSettingsSchemaQml::GSettingsSchemaQml(QObject *parent): QObject(parent) {
177}
183}
178
184
@@ -376,6 +382,14 @@ QVariant GSettingsQml::enableIndicatorMenu() const
376 }
382 }
377}
383}
378
384
385+QVariant GSettingsQml::appstoreUri() const
386+{
387+ if (m_valid && m_schema->id() == "com.canonical.Unity8") {
388+ return GSettingsControllerQml::instance()->appstoreUri();
389+ }
390+ return QVariant();
391+}
392+
379void GSettingsQml::setLifecycleExemptAppids(const QVariant &appIds)
393void GSettingsQml::setLifecycleExemptAppids(const QVariant &appIds)
380{
394{
381 if (m_valid && m_schema->id() == "com.canonical.qtmir") {
395 if (m_valid && m_schema->id() == "com.canonical.qtmir") {
tests/mocks/GSettings.1.0/fake_gsettings.h+7
@@ -59,6 +59,7 @@ class GSettingsQml: public QObject, public QQmlParserStatus
59 Q_PROPERTY(QVariant edgeDragWidth READ edgeDragWidth WRITE setEdgeDragWidth NOTIFY edgeDragWidthChanged)
59 Q_PROPERTY(QVariant edgeDragWidth READ edgeDragWidth WRITE setEdgeDragWidth NOTIFY edgeDragWidthChanged)
60 Q_PROPERTY(QVariant enableLauncher READ enableLauncher WRITE setEnableLauncher NOTIFY enableLauncherChanged)
60 Q_PROPERTY(QVariant enableLauncher READ enableLauncher WRITE setEnableLauncher NOTIFY enableLauncherChanged)
61 Q_PROPERTY(QVariant enableIndicatorMenu READ enableIndicatorMenu WRITE setEnableIndicatorMenu NOTIFY enableIndicatorMenuChanged)
61 Q_PROPERTY(QVariant enableIndicatorMenu READ enableIndicatorMenu WRITE setEnableIndicatorMenu NOTIFY enableIndicatorMenuChanged)
62+ Q_PROPERTY(QVariant appstoreUri READ appstoreUri NOTIFY appstoreUriChanged)
62
63
63public:
64public:
64 GSettingsQml(QObject *parent = nullptr);
65 GSettingsQml(QObject *parent = nullptr);
@@ -77,6 +78,7 @@ public:
77 QVariant edgeDragWidth() const;
78 QVariant edgeDragWidth() const;
78 QVariant enableLauncher() const;
79 QVariant enableLauncher() const;
79 QVariant enableIndicatorMenu() const;
80 QVariant enableIndicatorMenu() const;
81+ QVariant appstoreUri() const;
80
82
81 void setDisableHeight(const QVariant &val);
83 void setDisableHeight(const QVariant &val);
82 void setPictureUri(const QVariant &str);
84 void setPictureUri(const QVariant &str);
@@ -101,6 +103,7 @@ Q_SIGNALS:
101 void edgeDragWidthChanged();
103 void edgeDragWidthChanged();
102 void enableLauncherChanged();
104 void enableLauncherChanged();
103 void enableIndicatorMenuChanged();
105 void enableIndicatorMenuChanged();
106+ void appstoreUriChanged();
104
107
105private:
108private:
106 GSettingsSchemaQml* m_schema;
109 GSettingsSchemaQml* m_schema;
@@ -147,6 +150,8 @@ public:
147 bool enableIndicatorMenu() const;
150 bool enableIndicatorMenu() const;
148 Q_INVOKABLE void setEnableIndicatorMenu(bool enableIndicatorMenu);
151 Q_INVOKABLE void setEnableIndicatorMenu(bool enableIndicatorMenu);
149
152
153+ QString appstoreUri() const;
154+
150Q_SIGNALS:
155Q_SIGNALS:
151 void disableHeightChanged();
156 void disableHeightChanged();
152 void pictureUriChanged(const QString&);
157 void pictureUriChanged(const QString&);
@@ -158,6 +163,7 @@ Q_SIGNALS:
158 void edgeDragWidthChanged(uint edgeDragWidth);
163 void edgeDragWidthChanged(uint edgeDragWidth);
159 void enableLauncherChanged(bool enableLauncher);
164 void enableLauncherChanged(bool enableLauncher);
160 void enableIndicatorMenuChanged(bool enableIndicatorMenu);
165 void enableIndicatorMenuChanged(bool enableIndicatorMenu);
166+ void appstoreUriChanged(const QString &appstoreUri);
161
167
162private:
168private:
163 GSettingsControllerQml();
169 GSettingsControllerQml();
@@ -172,6 +178,7 @@ private:
172 uint m_edgeDragWidth;
178 uint m_edgeDragWidth;
173 bool m_enableLauncher;
179 bool m_enableLauncher;
174 bool m_enableIndicatorMenu;
180 bool m_enableIndicatorMenu;
181+ QString m_appstoreUri;
175
182
176 static GSettingsControllerQml* s_controllerInstance;
183 static GSettingsControllerQml* s_controllerInstance;
177 QList<GSettingsQml *> m_registeredGSettings;
184 QList<GSettingsQml *> m_registeredGSettings;
tests/mocks/LightDM/CMakeLists.txtdeleted−1
@@ -1 +0,0 @@
1−add_subdirectory(IntegratedLightDM)
tests/mocks/LightDM/IntegratedLightDM/CMakeLists.txtdeleted−50
@@ -1,50 +0,0 @@
1−# This is a copy of the normal LightDM plugin, but instead of statically
2−# linking in the lightdm bits, this one uses shared libraries so we can swap
3−# out different sets of users for different tests. When we finally switch to
4−# actually using the system liblightdm in the normal plugin, this version can
5−# be deleted.
6−
7−add_subdirectory(liblightdm)
8−
9−include_directories(
10− ${CMAKE_CURRENT_SOURCE_DIR}
11− ${CMAKE_CURRENT_BINARY_DIR}
12− ${CMAKE_SOURCE_DIR}/plugins/Utils
13− ${CMAKE_SOURCE_DIR}/plugins/LightDM
14− ${CMAKE_SOURCE_DIR}/tests/mocks/libusermetrics
15− ${libunity8-private_SOURCE_DIR}
16−)
17−
18−set(QMLPLUGIN_SRC
19− ${CMAKE_SOURCE_DIR}/plugins/LightDM/DBusGreeter.cpp
20− ${CMAKE_SOURCE_DIR}/plugins/LightDM/DBusGreeterList.cpp
21− ${CMAKE_SOURCE_DIR}/plugins/LightDM/Greeter.cpp
22− ${CMAKE_SOURCE_DIR}/plugins/LightDM/SessionsModel.cpp
23− ${CMAKE_SOURCE_DIR}/plugins/LightDM/UsersModel.cpp
24− ${CMAKE_SOURCE_DIR}/plugins/Utils/unitysortfilterproxymodelqml.cpp
25− MockGreeter.cpp
26− MockSessionsModel.cpp
27− MockUsersModel.cpp
28− plugin.cpp
29− )
30−
31−add_library(MockLightDM-qml MODULE
32− ${QMLPLUGIN_SRC}
33− )
34−
35−# We want to link to liblightdm-qt5-3, but we don't want to depend on it being
36−# installed on the system. So we make sure we link to our full fake version
37−# At run time, we can point to whichever version we happen to be using via
38−# LD_LIBRARY_PATH.
39−target_link_libraries(MockLightDM-qml
40− MockLightDM
41− MockUserMetrics
42− unity8-private
43− )
44−
45−qt5_use_modules(MockLightDM-qml DBus Gui Qml)
46−
47−add_unity8_mock(LightDM.IntegratedLightDM 0.1 LightDM/IntegratedLightDM
48− TARGETS MockLightDM-qml
49− ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_CURRENT_BINARY_DIR}/liblightdm"
50−)
tests/mocks/LightDM/IntegratedLightDM/MockGreeter.cppdeleted−51
@@ -1,51 +0,0 @@
1−/*
2− * Copyright (C) 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− */
17−
18−#include "MockGreeter.h"
19−#include <GreeterPrivate.h>
20−
21−QString MockGreeter::mockMode() const
22−{
23− Q_D(const Greeter);
24− return d->m_greeter->mockMode();
25−}
26−
27−void MockGreeter::setMockMode(QString mockMode)
28−{
29− Q_D(Greeter);
30−
31− if (d->m_greeter->mockMode() != mockMode) {
32− d->m_greeter->setMockMode(mockMode);
33− Q_EMIT mockModeChanged(mockMode);
34− }
35−}
36−
37−QString MockGreeter::selectUserHint() const
38−{
39− Q_D(const Greeter);
40− return d->m_greeter->selectUserHint();
41−}
42−
43−void MockGreeter::setSelectUserHint(const QString &selectUserHint)
44−{
45− Q_D(Greeter);
46−
47− if (d->m_greeter->selectUserHint() != selectUserHint) {
48− d->m_greeter->setSelectUserHint(selectUserHint);
49− Q_EMIT selectUserHintChanged();
50− }
51−}
tests/mocks/LightDM/IntegratedLightDM/MockGreeter.hdeleted−42
@@ -1,42 +0,0 @@
1−/*
2− * Copyright (C) 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− */
17−
18−// The real, production, Greeter
19−#include <Greeter.h>
20−
21−#ifndef MOCK_UNITY_GREETER_H
22−#define MOCK_UNITY_GREETER_H
23−
24−class MockGreeter : public Greeter {
25− Q_OBJECT
26−
27− Q_PROPERTY(QString mockMode READ mockMode WRITE setMockMode NOTIFY mockModeChanged)
28− Q_PROPERTY(QString selectUser READ selectUserHint WRITE setSelectUserHint NOTIFY selectUserHintChanged)
29−
30−public:
31− QString mockMode() const;
32− void setMockMode(QString mockMode);
33−
34− QString selectUserHint() const;
35− void setSelectUserHint(const QString &selectUserHint);
36−
37−Q_SIGNALS:
38− void mockModeChanged(QString mode);
39− void selectUserHintChanged();
40−};
41−
42−#endif // MOCK_UNITY_GREETER_H
tests/mocks/LightDM/IntegratedLightDM/MockSessionsModel.cppdeleted−66
@@ -1,66 +0,0 @@
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−
18−#include "MockSessionsModel.h"
19−#include <QLightDM/SessionsModel>
20−
21−
22−int MockSessionsModel::numSessions() const
23−{
24− QLightDM::SessionsModel* qSessionsModel =
25− static_cast<QLightDM::SessionsModel*>(sourceModel());
26−
27− return qSessionsModel->numSessions();
28−}
29−
30−int MockSessionsModel::numAvailableSessions() const
31−{
32− QLightDM::SessionsModel* qSessionsModel =
33− static_cast<QLightDM::SessionsModel*>(sourceModel());
34−
35− return qSessionsModel->numAvailableSessions();
36−}
37−
38−QString MockSessionsModel::testScenario() const
39−{
40− QLightDM::SessionsModel* qSessionsModel =
41− static_cast<QLightDM::SessionsModel*>(sourceModel());
42−
43− return qSessionsModel->testScenario();
44−}
45−
46−void MockSessionsModel::setNumSessions(const int numSessions)
47−{
48− QLightDM::SessionsModel* qSessionsModel =
49− static_cast<QLightDM::SessionsModel*>(sourceModel());
50−
51− if (qSessionsModel->numSessions() != numSessions) {
52− qSessionsModel->setNumSessions(numSessions);
53− Q_EMIT numSessionsChanged();
54− }
55−}
56−
57−void MockSessionsModel::setTestScenario(const QString testScenario)
58−{
59− QLightDM::SessionsModel* qSessionsModel =
60− static_cast<QLightDM::SessionsModel*>(sourceModel());
61−
62− if (qSessionsModel->testScenario() != testScenario) {
63− qSessionsModel->setTestScenario(testScenario);
64− Q_EMIT testScenarioChanged();
65− }
66−}
tests/mocks/LightDM/IntegratedLightDM/MockSessionsModel.hdeleted−44
@@ -1,44 +0,0 @@
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−
18−#ifndef MOCK_UNITY_SESSIONSMODEL_H
19−#define MOCK_UNITY_SESSIONSMODEL_H
20−
21−#include <SessionsModel.h>
22−
23−class MockSessionsModel : public SessionsModel
24−{
25− Q_OBJECT
26−
27− Q_PROPERTY(int numAvailableSessions READ numAvailableSessions CONSTANT)
28− Q_PROPERTY(int numSessions READ numSessions WRITE setNumSessions NOTIFY numSessionsChanged)
29− Q_PROPERTY(QString testScenario READ testScenario WRITE setTestScenario NOTIFY testScenarioChanged)
30−
31−public:
32− int numAvailableSessions() const;
33− int numSessions() const;
34− QString testScenario() const;
35− void setNumSessions(const int numSessions);
36− void setTestScenario(const QString testScenario);
37−
38−Q_SIGNALS:
39− void numSessionsChanged();
40− void testScenarioChanged();
41−
42−};
43−
44−#endif // MOCK_UNITY_SESSIONSMODEL_H
tests/mocks/LightDM/IntegratedLightDM/MockUsersModel.cppdeleted−44
@@ -1,44 +0,0 @@
1−/*
2− * Copyright (C) 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− */
17−
18−#include "MockUsersModel.h"
19−#include <QLightDM/UsersModel>
20−#include <QSortFilterProxyModel>
21−
22−MockUsersModel::MockUsersModel(QObject* parent)
23− : UsersModel(parent)
24−{
25−}
26−
27−QString MockUsersModel::mockMode() const
28−{
29− QLightDM::UsersModel* qUsersModel =
30− static_cast<QLightDM::UsersModel*>(static_cast<QSortFilterProxyModel*>(sourceModel())->sourceModel());
31−
32− return qUsersModel->mockMode();
33−}
34−
35−void MockUsersModel::setMockMode(QString mockMode)
36−{
37− QLightDM::UsersModel* qUsersModel =
38− static_cast<QLightDM::UsersModel*>(static_cast<QSortFilterProxyModel*>(sourceModel())->sourceModel());
39−
40− if (qUsersModel->mockMode() != mockMode) {
41− qUsersModel->setMockMode(mockMode);
42− Q_EMIT mockModeChanged(mockMode);
43− }
44−}
tests/mocks/LightDM/IntegratedLightDM/QLightDM/Greeterdeleted−17
@@ -1,17 +0,0 @@
1−/*
2− * Copyright (C) 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−
17−#include "../liblightdm/Greeter.h"
tests/mocks/LightDM/IntegratedLightDM/QLightDM/SessionsModeldeleted−17
@@ -1,17 +0,0 @@
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 "../liblightdm/SessionsModel.h"
tests/mocks/LightDM/IntegratedLightDM/QLightDM/UsersModeldeleted−17
@@ -1,17 +0,0 @@
1−/*
2− * Copyright (C) 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−
17−#include "../liblightdm/UsersModel.h"
tests/mocks/LightDM/IntegratedLightDM/liblightdm/CMakeLists.txtdeleted−21
@@ -1,21 +0,0 @@
1−set(LibLightDM_SOURCES
2− Greeter.cpp
3− SessionsModel.cpp
4− UsersModel.cpp
5− GreeterPrivate.cpp
6− SessionsModelPrivate.cpp
7− UsersModelPrivate.cpp
8− ${CMAKE_SOURCE_DIR}/plugins/Utils/qvariantlistmodel.cpp
9− )
10−
11−add_library(MockLightDM SHARED ${LibLightDM_SOURCES})
12−
13−qt5_use_modules(MockLightDM Gui)
14−
15−set_target_properties(MockLightDM PROPERTIES
16− OUTPUT_NAME lightdm-qt5-3
17− SOVERSION 0)
18−
19−install(TARGETS MockLightDM
20− DESTINATION ${SHELL_INSTALL_QML}/mocks/LightDM/IntegratedLightDM/liblightdm
21− )
tests/mocks/LightDM/IntegratedLightDM/liblightdm/Greeter.cppdeleted−201
@@ -1,201 +0,0 @@
1−/*
2− * Copyright (C) 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−
17−#include "Greeter.h"
18−#include "GreeterPrivate.h"
19−#include <QtCore/QCoreApplication>
20−#include <QTimer>
21−
22−namespace QLightDM
23−{
24−
25−Greeter::Greeter(QObject *parent)
26− : QObject(parent),
27− d_ptr(new GreeterPrivate(this))
28−{
29−}
30−
31−Greeter::~Greeter()
32−{
33−}
34−
35−QString Greeter::authenticationUser() const
36−{
37− Q_D(const Greeter);
38− return d->authenticationUser;
39−}
40−
41−bool Greeter::hasGuestAccountHint() const
42−{
43− return true;
44−}
45−
46−QString Greeter::getHint(const QString &name) const
47−{
48− Q_UNUSED(name)
49− return "";
50−}
51−
52−QString Greeter::defaultSessionHint() const
53−{
54− return QStringLiteral("ubuntu");
55−}
56−
57−bool Greeter::hideUsersHint() const
58−{
59− return false;
60−}
61−
62−bool Greeter::showManualLoginHint() const
63−{
64− return true;
65−}
66−
67−bool Greeter::showRemoteLoginHint() const
68−{
69− return true;
70−}
71−
72−QString Greeter::selectUserHint() const
73−{
74− Q_D(const Greeter);
75− return d->selectUserHint;
76−}
77−
78−void Greeter::setSelectUserHint(const QString &selectUserHint)
79−{
80− Q_D(Greeter);
81− d->selectUserHint = selectUserHint;
82−}
83−
84−bool Greeter::selectGuestHint() const
85−{
86− return false;
87−}
88−
89−QString Greeter::autologinUserHint() const
90−{
91− return "";
92−}
93−
94−bool Greeter::autologinGuestHint() const
95−{
96− return false;
97−}
98−
99−int Greeter::autologinTimeoutHint() const
100−{
101− return 0;
102−}
103−
104−bool Greeter::inAuthentication() const
105−{
106− return false;
107−}
108−
109−QString Greeter::hostname() const
110−{
111− return "hostname1";
112−}
113−
114−bool Greeter::isAuthenticated() const
115−{
116− Q_D(const Greeter);
117− return d->authenticated;
118−}
119−
120−bool Greeter::connectSync()
121−{
122− return true;
123−}
124−
125−void Greeter::authenticate(const QString &username)
126−{
127− Q_D(Greeter);
128−
129− d->authenticated = false;
130− d->authenticationUser = username;
131− d->twoFactorDone = false;
132− QTimer::singleShot(0, this, &Greeter::delayedAuthentication);
133−}
134−
135−void Greeter::delayedAuthentication()
136−{
137− Q_D(Greeter);
138− d->handleAuthenticate();
139−}
140−
141−void Greeter::authenticateAsGuest()
142−{}
143−
144−void Greeter::authenticateAutologin()
145−{}
146−
147−void Greeter::authenticateRemote(const QString &session, const QString &username)
148−{
149− Q_UNUSED(session)
150− Q_UNUSED(username)
151−}
152−
153−void Greeter::cancelAuthentication()
154−{}
155−
156−void Greeter::setLanguage (const QString &language)
157−{
158− Q_UNUSED(language)
159−}
160−
161−bool Greeter::startSessionSync(const QString &session)
162−{
163− Q_UNUSED(session)
164− return true;
165−}
166−
167−void Greeter::respond(const QString &response)
168−{
169− Q_D(Greeter);
170−
171− d->handleRespond(response);
172−}
173−
174−void Greeter::sendAuthenticationComplete()
175−{
176− if (qEnvironmentVariableIsEmpty("UNITY_TESTING")) {
177− // simulate PAM's delay
178− QTimer::singleShot(1000, this, &Greeter::authenticationComplete);
179− } else {
180− Q_EMIT authenticationComplete();
181− }
182−}
183−
184−QString Greeter::mockMode() const
185−{
186− Q_D(const Greeter);
187− return d->mockMode;
188−}
189−
190−
191−void Greeter::setMockMode(QString mockMode)
192−{
193− Q_D(Greeter);
194−
195− if (d->mockMode != mockMode) {
196− d->mockMode = mockMode;
197− Q_EMIT mockModeChanged(mockMode);
198− }
199−}
200−
201−}
tests/mocks/LightDM/IntegratedLightDM/liblightdm/UsersModel.cppdeleted−121
@@ -1,121 +0,0 @@
1−/*
2− * Copyright (C) 2013-2016 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− * Author: Michael Terry <michael.terry@canonical.com>
17− */
18−
19−// LightDM currently is Qt4 compatible, and so doesn't define setRoleNames.
20−// To use the same method of setting role name that it does, we
21−// set our compatibility to Qt4 here too.
22−#define QT_DISABLE_DEPRECATED_BEFORE QT_VERSION_CHECK(4, 0, 0)
23−
24−#include "UsersModel.h"
25−#include "UsersModelPrivate.h"
26−#include <QtCore/QDir>
27−#include <QtCore/QString>
28−#include <QtGui/QIcon>
29−
30−namespace QLightDM
31−{
32−
33−UsersModel::UsersModel(QObject *parent) :
34− QAbstractListModel(parent),
35− d_ptr(new UsersModelPrivate(this))
36−{
37− // Extend roleNames (we want to keep the "display" role)
38− QHash<int, QByteArray> roles = roleNames();
39− roles[NameRole] = "name";
40− roles[RealNameRole] = "realName";
41− roles[LoggedInRole] = "loggedIn";
42− roles[BackgroundRole] = "background";
43− roles[BackgroundPathRole] = "backgroundPath";
44− roles[SessionRole] = "session";
45− roles[HasMessagesRole] = "hasMessages";
46− roles[ImagePathRole] = "imagePath";
47− roles[UidRole] = "uid";
48− setRoleNames(roles);
49−}
50−
51−UsersModel::~UsersModel()
52−{
53− delete d_ptr;
54−}
55−
56−int UsersModel::rowCount(const QModelIndex &parent) const
57−{
58− Q_D(const UsersModel);
59−
60− if (parent.isValid()) {
61− return 0;
62− } else { // parent is root
63− return d->entries.size();
64− }
65−}
66−
67−QVariant UsersModel::data(const QModelIndex &index, int role) const
68−{
69− Q_D(const UsersModel);
70−
71− if (!index.isValid()) {
72− return QVariant();
73− }
74−
75− int row = index.row();
76− switch (role) {
77− case Qt::DisplayRole:
78− return d->entries[row].real_name;
79− case Qt::DecorationRole:
80− return QIcon();
81− case UsersModel::NameRole:
82− return d->entries[row].username;
83− case UsersModel::RealNameRole:
84− return d->entries[row].real_name;
85− case UsersModel::SessionRole:
86− return d->entries[row].session;
87− case UsersModel::LoggedInRole:
88− return d->entries[row].is_active;
89− case UsersModel::BackgroundRole:
90− return QPixmap(d->entries[row].background);
91− case UsersModel::BackgroundPathRole:
92− return d->entries[row].background;
93− case UsersModel::HasMessagesRole:
94− return d->entries[row].has_messages;
95− case UsersModel::ImagePathRole:
96− return "";
97− case UsersModel::UidRole:
98− return d->entries[row].uid;
99− default:
100− return QVariant();
101− }
102−}
103−
104−QString UsersModel::mockMode() const
105−{
106− Q_D(const UsersModel);
107− return d->mockMode;
108−}
109−
110−void UsersModel::setMockMode(QString mockMode)
111−{
112− Q_D(UsersModel);
113−
114− if (d->mockMode != mockMode) {
115− d->mockMode = mockMode;
116− Q_EMIT mockModeChanged(mockMode);
117− d->resetEntries();
118− }
119−}
120−
121−}
tests/mocks/LightDM/IntegratedLightDM/plugin.cppdeleted−79
@@ -1,79 +0,0 @@
1−/*
2− * Copyright (C) 2014, 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 "plugin.h"
18−#include <DBusGreeter.h>
19−#include <DBusGreeterList.h>
20−#include "MockGreeter.h"
21−#include "MockSessionsModel.h"
22−#include "MockUsersModel.h"
23−#include <QLightDM/SessionsModel>
24−#include "ColorTheme.h"
25−#include "UserMetrics.h"
26−#include <QLightDM/UsersModel>
27−
28−#include <QAbstractItemModel>
29−#include <QDBusConnection>
30−#include <QtQml/qqml.h>
31−
32−static QObject *greeter_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
33−{
34− Q_UNUSED(engine)
35− Q_UNUSED(scriptEngine)
36−
37− MockGreeter *greeter = new MockGreeter;
38− new DBusGreeter(greeter, "/");
39− new DBusGreeterList(greeter, "/list");
40−
41− return greeter;
42−}
43−
44−static QObject *sessions_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
45−{
46− Q_UNUSED(engine)
47− Q_UNUSED(scriptEngine)
48− return new MockSessionsModel;
49−}
50−
51−static QObject *users_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
52−{
53− Q_UNUSED(engine)
54− Q_UNUSED(scriptEngine)
55− return new MockUsersModel;
56−}
57−
58−static QObject *infographic_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
59−{
60− Q_UNUSED(engine)
61− Q_UNUSED(scriptEngine)
62− return UserMetricsOutput::UserMetrics::getInstance();
63−}
64−
65−void LightDMPlugin::registerTypes(const char *uri)
66−{
67− qmlRegisterType<QAbstractItemModel>();
68− qmlRegisterType<UserMetricsOutput::ColorTheme>();
69−
70− Q_ASSERT(uri == QLatin1String("LightDM.IntegratedLightDM"));
71− qRegisterMetaType<QLightDM::Greeter::MessageType>("QLightDM::Greeter::MessageType");
72− qRegisterMetaType<QLightDM::Greeter::PromptType>("QLightDM::Greeter::PromptType");
73− qmlRegisterSingletonType<MockGreeter>(uri, 0, 1, "Greeter", greeter_provider);
74− qmlRegisterSingletonType<MockSessionsModel>(uri, 0, 1, "Sessions", sessions_provider);
75− qmlRegisterUncreatableType<QLightDM::SessionsModel>(uri, 0, 1, "SessionRoles", "Type is not instantiable");
76− qmlRegisterSingletonType<MockUsersModel>(uri, 0, 1, "Users", users_provider);
77− qmlRegisterUncreatableType<QLightDM::UsersModel>(uri, 0, 1, "UserRoles", "Type is not instantiable");
78− qmlRegisterSingletonType<UserMetricsOutput::UserMetrics>(uri, 0, 1, "Infographic", infographic_provider);
79−}
tests/mocks/LightDM/IntegratedLightDM/qmldirdeleted−2
@@ -1,2 +0,0 @@
1−module LightDM.IntegratedLightDM
2−plugin MockLightDM-qml
tests/mocks/LightDMController/CMakeLists.txtnew file+15
@@ -0,0 +1,15 @@
1+add_library(LightDMController MODULE
2+ plugin.cpp
3+ )
4+
5+target_include_directories(LightDMController PUBLIC
6+ ${CMAKE_SOURCE_DIR}/tests/mocks/liblightdm/
7+ )
8+
9+target_link_libraries(LightDMController
10+ MockLightDM
11+ )
12+
13+qt5_use_modules(LightDMController Qml)
14+
15+add_unity8_mock(LightDMController 0.1 LightDMController TARGETS LightDMController)
tests/mocks/LightDM/IntegratedLightDM/MockUsersModel.h → tests/mocks/LightDMController/plugin.cpprenamed+16−20
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2017 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -12,28 +12,24 @@
12 *
12 *
13 * You should have received a copy of the GNU General Public License
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/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16 */
15 */
17
16
18−#ifndef MOCK_UNITY_USERSMODEL_H
19−#define MOCK_UNITY_USERSMODEL_H
20−
21−#include <UsersModel.h>
17+#include "plugin.h"
22−
23−class MockUsersModel : public UsersModel
24−{
25− Q_OBJECT
26
18
27− Q_PROPERTY(QString mockMode READ mockMode WRITE setMockMode NOTIFY mockModeChanged)
19+#include "MockController.h"
28
20
29−public:
21+#include <QtQml/qqml.h>
30− explicit MockUsersModel(QObject* parent=0);
31
22
32− QString mockMode() const;
23+static QObject *mock_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
33− void setMockMode(QString mockMode);
24+{
34−
25+ Q_UNUSED(scriptEngine)
35−Q_SIGNALS:
26+ auto controller = QLightDM::MockController::instance();
36− void mockModeChanged(QString mode);
27+ engine->setObjectOwnership(controller, QQmlEngine::CppOwnership);
28+ return controller;
37−};
29+}
38
30
39−#endif // MOCK_UNITY_USERSMODEL_H
31+void LightDMControllerPlugin::registerTypes(const char *uri)
32+{
33+ Q_ASSERT(uri == QLatin1String("LightDMController"));
34+ qmlRegisterSingletonType<QLightDM::MockController>(uri, 0, 1, "LightDMController", mock_provider);
35+}
tests/mocks/LightDM/IntegratedLightDM/plugin.h → tests/mocks/LightDMController/plugin.hrenamed+5−8
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2014, 2015 Canonical, Ltd.
2+ * Copyright (C) 2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -14,13 +14,12 @@
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 */
16
16
17−#ifndef MOCK_LIGHTDM_PLUGIN_H
17+#pragma once
18−#define MOCK_LIGHTDM_PLUGIN_H
19
18
20−#include <QtQml/QQmlEngine>
19+#include <QQmlEngine>
21−#include <QtQml/QQmlExtensionPlugin>
20+#include <QQmlExtensionPlugin>
22
21
23−class LightDMPlugin : public QQmlExtensionPlugin
22+class LightDMControllerPlugin : public QQmlExtensionPlugin
24{
23{
25 Q_OBJECT
24 Q_OBJECT
26 Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
25 Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
@@ -28,5 +27,3 @@ class LightDMPlugin : public QQmlExtensionPlugin
28public:
27public:
29 void registerTypes(const char *uri) override;
28 void registerTypes(const char *uri) override;
30};
29};
31−
32−#endif
tests/mocks/LightDMController/qmldirnew file+2
@@ -0,0 +1,2 @@
1+module LightDMController
2+plugin LightDMController
tests/mocks/Unity/Application/MirMock.cpp+13
@@ -47,3 +47,16 @@ QString MirMock::cursorName() const
47{
47{
48 return m_cursorName;
48 return m_cursorName;
49}
49}
50+
51+QString MirMock::currentKeymap() const
52+{
53+ return m_keymap;
54+}
55+
56+void MirMock::setCurrentKeymap(const QString &keymap)
57+{
58+ if (keymap != m_keymap) {
59+ m_keymap = keymap;
60+ Q_EMIT currentKeymapChanged(m_keymap);
61+ }
62+}
tests/mocks/Unity/Application/MirMock.h+4
@@ -31,9 +31,13 @@ public:
31 void setCursorName(const QString &cursorName) override;
31 void setCursorName(const QString &cursorName) override;
32 QString cursorName() const override;
32 QString cursorName() const override;
33
33
34+ QString currentKeymap() const override;
35+ void setCurrentKeymap(const QString &keymap) override;
36+
34private:
37private:
35 static MirMock *the_mir;
38 static MirMock *the_mir;
36 QString m_cursorName;
39 QString m_cursorName;
40+ QString m_keymap;
37};
41};
38
42
39#endif
43#endif
tests/mocks/Unity/Launcher/MockAppDrawerModel.cpp−1
@@ -48,7 +48,6 @@ MockAppDrawerModel::MockAppDrawerModel(QObject *parent):
48 m_list.append(item);
48 m_list.append(item);
49 item = new MockLauncherItem("libreoffice", "/usr/share/applications/libreoffice.desktop","Libre Office", "libreoffice", this);
49 item = new MockLauncherItem("libreoffice", "/usr/share/applications/libreoffice.desktop","Libre Office", "libreoffice", this);
50 m_list.append(item);
50 m_list.append(item);
51− qDebug() << "mock model created";
52
51
53 qsrand(QDateTime::currentMSecsSinceEpoch() / 1000);
52 qsrand(QDateTime::currentMSecsSinceEpoch() / 1000);
54}
53}
tests/mocks/Utils/CMakeLists.txt+1
@@ -25,6 +25,7 @@ set(QMLPLUGIN_SRC
25 ${CMAKE_SOURCE_DIR}/plugins/Utils/deviceconfigparser.cpp
25 ${CMAKE_SOURCE_DIR}/plugins/Utils/deviceconfigparser.cpp
26 ${CMAKE_SOURCE_DIR}/plugins/Utils/globalfunctions.cpp
26 ${CMAKE_SOURCE_DIR}/plugins/Utils/globalfunctions.cpp
27 ${CMAKE_SOURCE_DIR}/plugins/Utils/appdrawerproxymodel.cpp
27 ${CMAKE_SOURCE_DIR}/plugins/Utils/appdrawerproxymodel.cpp
28+ ${CMAKE_SOURCE_DIR}/plugins/Utils/tabfocusfence.cpp
28 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
29 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
29 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
30 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
30 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
31 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
tests/mocks/Utils/plugin.cpp+3−1
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2015-2016 Canonical, Ltd.
2+ * Copyright (C) 2015-2017 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -41,6 +41,7 @@
41#include <deviceconfigparser.h>
41#include <deviceconfigparser.h>
42#include <globalfunctions.h>
42#include <globalfunctions.h>
43#include <appdrawerproxymodel.h>
43#include <appdrawerproxymodel.h>
44+#include <tabfocusfence.h>
44
45
45static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
46static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
46{
47{
@@ -84,4 +85,5 @@ void FakeUtilsPlugin::registerTypes(const char *uri)
84 qmlRegisterSingletonType<GlobalFunctions>(uri, 0, 1, "Functions", createGlobalFunctions);
85 qmlRegisterSingletonType<GlobalFunctions>(uri, 0, 1, "Functions", createGlobalFunctions);
85 qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher");
86 qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher");
86 qmlRegisterType<AppDrawerProxyModel>(uri, 0, 1, "AppDrawerProxyModel");
87 qmlRegisterType<AppDrawerProxyModel>(uri, 0, 1, "AppDrawerProxyModel");
88+ qmlRegisterType<TabFocusFenceItem>(uri, 0, 1, "TabFocusFence");
87}
89}
tests/mocks/liblightdm/CMakeLists.txtnew file+18
@@ -0,0 +1,18 @@
1+set(MockLightDM_SOURCES
2+ MockController.cpp
3+ MockGreeter.cpp
4+ MockSessionsModel.cpp
5+ MockUsersModel.cpp
6+ )
7+
8+add_library(MockLightDM SHARED ${MockLightDM_SOURCES})
9+
10+qt5_use_modules(MockLightDM DBus Gui)
11+
12+set_target_properties(MockLightDM PROPERTIES
13+ OUTPUT_NAME lightdm-qt5-3
14+ SOVERSION 0)
15+
16+install(TARGETS MockLightDM
17+ DESTINATION ${SHELL_INSTALL_QML}/mocks/liblightdm
18+ )
tests/mocks/LightDM/IntegratedLightDM/liblightdm/GreeterPrivate.cpp → tests/mocks/liblightdm/GreeterPrivate.cpprenamed

The content is the same.

tests/mocks/LightDM/IntegratedLightDM/liblightdm/GreeterPrivate.h → tests/mocks/liblightdm/GreeterPrivate.hrenamed

The content is the same.

tests/mocks/liblightdm/MockController.cppnew file+191
@@ -0,0 +1,191 @@
1+/*
2+ * Copyright (C) 2016 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 "MockController.h"
18+
19+static QLightDM::MockController *m_instance = nullptr;
20+
21+namespace QLightDM
22+{
23+
24+MockController::MockController(QObject *parent)
25+ : QObject(parent)
26+ , m_selectGuestHint(false)
27+ , m_hasGuestAccountHint(false)
28+ , m_fullSessions(
29+ {
30+ {"ubuntu", "Ubuntu"},
31+ {"ubuntu-2d", "Ubuntu 2D"},
32+ {"gnome", "GNOME"},
33+ {"gnome-classic", "GNOME Classic"},
34+ {"gnome-flashback-compiz", "GNOME Flashback (Compiz)"},
35+ {"gnome-flashback-metacity", "GNOME Flashback (Metacity)"},
36+ {"gnome-wayland", "GNOME on Wayland"},
37+ {"plasma", "Plasma"},
38+ {"kde", "KDE" },
39+ {"xterm", "Recovery Console"},
40+ {"", "Unknown?"}
41+ })
42+ , m_numSessions(0)
43+{
44+ reset();
45+}
46+
47+MockController::~MockController()
48+{
49+ m_instance = nullptr;
50+}
51+
52+MockController *MockController::instance()
53+{
54+ if (!m_instance) {
55+ m_instance = new MockController;
56+ }
57+ return m_instance;
58+}
59+
60+void MockController::reset()
61+{
62+ auto userMode = qgetenv("LIBLIGHTDM_MOCK_MODE");
63+ if (userMode.isEmpty()) {
64+ userMode = "full";
65+ }
66+
67+ setSelectUserHint("");
68+ setSelectGuestHint(false);
69+ setHasGuestAccountHint(false);
70+ setUserMode(userMode);
71+ setSessionMode("full");
72+ setNumSessions(numFullSessions());
73+ setShowManualLoginHint(false);
74+ setHideUsersHint(false);
75+}
76+
77+QString MockController::selectUserHint() const
78+{
79+ return m_selectUserHint;
80+}
81+
82+void MockController::setSelectUserHint(const QString &selectUserHint)
83+{
84+ if (m_selectUserHint != selectUserHint) {
85+ m_selectUserHint = selectUserHint;
86+ Q_EMIT selectUserHintChanged();
87+ }
88+}
89+
90+bool MockController::selectGuestHint() const
91+{
92+ return m_selectGuestHint;
93+}
94+
95+void MockController::setSelectGuestHint(bool selectGuestHint)
96+{
97+ if (m_selectGuestHint != selectGuestHint) {
98+ m_selectGuestHint = selectGuestHint;
99+ Q_EMIT selectGuestHintChanged();
100+ }
101+}
102+
103+bool MockController::hasGuestAccountHint() const
104+{
105+ return m_hasGuestAccountHint;
106+}
107+
108+void MockController::setHasGuestAccountHint(bool hasGuestAccountHint)
109+{
110+ if (m_hasGuestAccountHint != hasGuestAccountHint) {
111+ m_hasGuestAccountHint = hasGuestAccountHint;
112+ Q_EMIT hasGuestAccountHintChanged();
113+ }
114+}
115+
116+bool MockController::showManualLoginHint() const
117+{
118+ return m_showManualLoginHint;
119+}
120+
121+void MockController::setShowManualLoginHint(bool showManualLoginHint)
122+{
123+ if (m_showManualLoginHint != showManualLoginHint) {
124+ m_showManualLoginHint = showManualLoginHint;
125+ Q_EMIT showManualLoginHintChanged();
126+ }
127+}
128+
129+bool MockController::hideUsersHint() const
130+{
131+ return m_hideUsersHint;
132+}
133+
134+void MockController::setHideUsersHint(bool hideUsersHint)
135+{
136+ if (m_hideUsersHint != hideUsersHint) {
137+ m_hideUsersHint = hideUsersHint;
138+ Q_EMIT hideUsersHintChanged();
139+ }
140+}
141+
142+QString MockController::userMode() const
143+{
144+ return m_userMode;
145+}
146+
147+void MockController::setUserMode(const QString &userMode)
148+{
149+ if (m_userMode != userMode) {
150+ m_userMode = userMode;
151+ Q_EMIT userModeChanged();
152+ }
153+}
154+
155+QString MockController::sessionMode() const
156+{
157+ return m_sessionMode;
158+}
159+
160+void MockController::setSessionMode(const QString &sessionMode)
161+{
162+ if (m_sessionMode != sessionMode) {
163+ m_sessionMode = sessionMode;
164+ Q_EMIT sessionModeChanged();
165+ }
166+}
167+
168+const QList<MockController::SessionItem> &MockController::fullSessionItems() const
169+{
170+ return m_fullSessions;
171+}
172+
173+int MockController::numFullSessions() const
174+{
175+ return m_fullSessions.size();
176+}
177+
178+int MockController::numSessions() const
179+{
180+ return m_numSessions;
181+}
182+
183+void MockController::setNumSessions(int numSessions)
184+{
185+ if (m_numSessions != numSessions) {
186+ m_numSessions = numSessions;
187+ Q_EMIT numSessionsChanged();
188+ }
189+}
190+
191+}
tests/mocks/liblightdm/MockController.hnew file+106
@@ -0,0 +1,106 @@
1+/*
2+ * Copyright (C) 2016 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+#pragma once
18+
19+#include <QObject>
20+#include <QString>
21+
22+
23+namespace QLightDM
24+{
25+class Q_DECL_EXPORT MockController : public QObject
26+{
27+ Q_OBJECT
28+
29+ Q_PROPERTY(QString selectUserHint READ selectUserHint WRITE setSelectUserHint NOTIFY selectUserHintChanged)
30+ Q_PROPERTY(bool selectGuestHint READ selectGuestHint WRITE setSelectGuestHint NOTIFY selectGuestHintChanged)
31+ Q_PROPERTY(bool hasGuestAccountHint READ hasGuestAccountHint WRITE setHasGuestAccountHint NOTIFY hasGuestAccountHintChanged)
32+ Q_PROPERTY(bool showManualLoginHint READ showManualLoginHint WRITE setShowManualLoginHint NOTIFY showManualLoginHintChanged)
33+ Q_PROPERTY(bool hideUsersHint READ hideUsersHint WRITE setHideUsersHint NOTIFY hideUsersHintChanged)
34+
35+ // single, single-pin, single-passphrase, full
36+ Q_PROPERTY(QString userMode READ userMode WRITE setUserMode NOTIFY userModeChanged)
37+
38+ // single, none, full
39+ Q_PROPERTY(QString sessionMode READ sessionMode WRITE setSessionMode NOTIFY sessionModeChanged)
40+
41+ Q_PROPERTY(int numAvailableSessions READ numFullSessions CONSTANT)
42+ Q_PROPERTY(int numSessions READ numSessions WRITE setNumSessions NOTIFY numSessionsChanged)
43+
44+public:
45+ static MockController *instance();
46+ virtual ~MockController();
47+
48+ Q_INVOKABLE void reset();
49+
50+ QString selectUserHint() const;
51+ void setSelectUserHint(const QString &selectUserHint);
52+
53+ bool selectGuestHint() const;
54+ void setSelectGuestHint(bool selectGuestHint);
55+
56+ bool hasGuestAccountHint() const;
57+ void setHasGuestAccountHint(bool hasGuestAccountHint);
58+
59+ bool showManualLoginHint() const;
60+ void setShowManualLoginHint(bool showManualLoginHint);
61+
62+ bool hideUsersHint() const;
63+ void setHideUsersHint(bool hideUsersHint);
64+
65+ QString userMode() const;
66+ void setUserMode(const QString &userMode);
67+
68+ QString sessionMode() const;
69+ void setSessionMode(const QString &sessionMode);
70+
71+ class SessionItem
72+ {
73+ public:
74+ QString key;
75+ QString name;
76+ };
77+ int numFullSessions() const;
78+ const QList<SessionItem> &fullSessionItems() const;
79+
80+ int numSessions() const;
81+ void setNumSessions(int numSessions);
82+
83+Q_SIGNALS:
84+ void selectUserHintChanged();
85+ void selectGuestHintChanged();
86+ void hasGuestAccountHintChanged();
87+ void showManualLoginHintChanged();
88+ void hideUsersHintChanged();
89+ void userModeChanged();
90+ void sessionModeChanged();
91+ void numSessionsChanged();
92+
93+private:
94+ explicit MockController(QObject* parent=0);
95+
96+ QString m_selectUserHint;
97+ bool m_selectGuestHint;
98+ bool m_hasGuestAccountHint;
99+ bool m_showManualLoginHint;
100+ bool m_hideUsersHint;
101+ QString m_userMode;
102+ QString m_sessionMode;
103+ QList<SessionItem> m_fullSessions;
104+ int m_numSessions;
105+};
106+}
tests/mocks/liblightdm/MockGreeter.cppnew file+316
@@ -0,0 +1,316 @@
1+/*
2+ * Copyright (C) 2014-2017 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 "MockController.h"
18+#include "MockGreeter.h"
19+#include <QDBusInterface>
20+#include <QDBusPendingCall>
21+#include <QTimer>
22+
23+namespace QLightDM
24+{
25+
26+class GreeterPrivate
27+{
28+public:
29+ bool authenticated = false;
30+ QString authenticationUser;
31+ bool twoFactorDone = false;
32+ QTimer authCallbackTimer;
33+};
34+
35+Greeter::Greeter(QObject *parent)
36+ : QObject(parent)
37+ , d_ptr(new GreeterPrivate)
38+{
39+ Q_D(Greeter);
40+
41+ d->authCallbackTimer.setSingleShot(true);
42+ connect(&d->authCallbackTimer, &QTimer::timeout,
43+ this, &QLightDM::Greeter::handleAuthenticate);
44+}
45+
46+Greeter::~Greeter()
47+{
48+ delete d_ptr;
49+}
50+
51+QString Greeter::authenticationUser() const
52+{
53+ Q_D(const Greeter);
54+
55+ return d->authenticationUser;
56+}
57+
58+bool Greeter::hasGuestAccountHint() const
59+{
60+ return MockController::instance()->hasGuestAccountHint();
61+}
62+
63+QString Greeter::getHint(const QString &name) const
64+{
65+ Q_UNUSED(name)
66+ return "";
67+}
68+
69+QString Greeter::defaultSessionHint() const
70+{
71+ return QStringLiteral("ubuntu");
72+}
73+
74+bool Greeter::hideUsersHint() const
75+{
76+ return MockController::instance()->hideUsersHint();
77+}
78+
79+bool Greeter::showManualLoginHint() const
80+{
81+ return MockController::instance()->showManualLoginHint();
82+}
83+
84+bool Greeter::showRemoteLoginHint() const
85+{
86+ return false;
87+}
88+
89+QString Greeter::selectUserHint() const
90+{
91+ return MockController::instance()->selectUserHint();
92+}
93+
94+bool Greeter::selectGuestHint() const
95+{
96+ return MockController::instance()->selectGuestHint();
97+}
98+
99+QString Greeter::autologinUserHint() const
100+{
101+ return "";
102+}
103+
104+bool Greeter::autologinGuestHint() const
105+{
106+ return false;
107+}
108+
109+int Greeter::autologinTimeoutHint() const
110+{
111+ return 0;
112+}
113+
114+bool Greeter::inAuthentication() const
115+{
116+ return false;
117+}
118+
119+QString Greeter::hostname() const
120+{
121+ return "hostname1";
122+}
123+
124+bool Greeter::isAuthenticated() const
125+{
126+ Q_D(const Greeter);
127+
128+ return d->authenticated;
129+}
130+
131+bool Greeter::connectSync()
132+{
133+ return true;
134+}
135+
136+void Greeter::authenticate(const QString &username)
137+{
138+ Q_D(Greeter);
139+
140+ d->authenticated = false;
141+ d->authenticationUser = username;
142+ d->twoFactorDone = false;
143+ d->authCallbackTimer.start();
144+}
145+
146+void Greeter::handleAuthenticate()
147+{
148+ Q_D(Greeter);
149+
150+ if (d->authenticated) {
151+ // This can happen from the guest authentication flow
152+ Q_EMIT authenticationComplete();
153+ return;
154+ }
155+
156+ if (d->authenticationUser.isEmpty()) {
157+ Q_EMIT showPrompt("Username: ", Greeter::PromptTypeQuestion);
158+ return;
159+ }
160+
161+ // Send out any messages we need to
162+ if (d->authenticationUser == "info-prompt")
163+ Q_EMIT showMessage("Welcome to Unity Greeter", Greeter::MessageTypeInfo);
164+ else if (d->authenticationUser == "wide-info-prompt")
165+ Q_EMIT showMessage("Welcome to Unity Greeter, the greeteriest greeter that ever did appear in these fine lands", Greeter::MessageTypeInfo);
166+ else if (d->authenticationUser == "html-info-prompt")
167+ Q_EMIT showMessage("<b>&</b>", Greeter::MessageTypeInfo);
168+ else if (d->authenticationUser == "long-info-prompt")
169+ Q_EMIT showMessage("Welcome to Unity Greeter\n\nWe like to annoy you with super ridiculously long messages.\nLike this one\n\nThis is the last line of a multiple line message.", Greeter::MessageTypeInfo);
170+ else if (d->authenticationUser == "multi-info-prompt") {
171+ Q_EMIT showMessage("Welcome to Unity Greeter", Greeter::MessageTypeInfo);
172+ Q_EMIT showMessage("This is an error", Greeter::MessageTypeError);
173+ Q_EMIT showMessage("You should have seen three messages", Greeter::MessageTypeInfo);
174+ }
175+
176+ // OK, now actually do the prompt
177+ if (d->authenticationUser == "no-password") {
178+ d->authenticated = true;
179+ Q_EMIT authenticationComplete();
180+ } else if (d->authenticationUser == "has-pin") {
181+ Q_EMIT showPrompt("Password: ", Greeter::PromptTypeSecret);
182+ } else if (d->authenticationUser == "auth-error") {
183+ d->authenticated = false;
184+ Q_EMIT authenticationComplete();
185+ } else if (d->authenticationUser == "different-prompt") {
186+ Q_EMIT showPrompt("Secret word: ", Greeter::PromptTypeSecret);
187+ } else if (d->authenticationUser == "question-prompt") {
188+ Q_EMIT showPrompt("Favorite Color (blue): ", Greeter::PromptTypeQuestion);
189+ } else if (d->authenticationUser == "two-prompts") {
190+ Q_EMIT showPrompt("Favorite Color (blue):", Greeter::PromptTypeQuestion);
191+ Q_EMIT showPrompt("Password: ", Greeter::PromptTypeSecret);
192+ } else if (d->authenticationUser == "wacky-prompts") {
193+ Q_EMIT showMessage("First message", Greeter::MessageTypeInfo);
194+ Q_EMIT showPrompt("Favorite Color (blue)", Greeter::PromptTypeQuestion);
195+ Q_EMIT showMessage("Second message", Greeter::MessageTypeError);
196+ Q_EMIT showPrompt("Password: ", Greeter::PromptTypeSecret);
197+ Q_EMIT showMessage("Last message", Greeter::MessageTypeInfo);
198+ } else {
199+ Q_EMIT showPrompt("Password: ", Greeter::PromptTypeSecret);
200+ }
201+}
202+
203+void Greeter::authenticateAsGuest()
204+{
205+ Q_D(Greeter);
206+
207+ d->authenticated = true;
208+ d->authenticationUser = QString(); // this is what the real liblightdm does
209+ d->twoFactorDone = false;
210+ d->authCallbackTimer.start();
211+}
212+
213+void Greeter::authenticateAutologin()
214+{}
215+
216+void Greeter::authenticateRemote(const QString &session, const QString &username)
217+{
218+ Q_UNUSED(session)
219+ Q_UNUSED(username)
220+}
221+
222+void Greeter::cancelAuthentication()
223+{
224+ Q_D(Greeter);
225+ d->authCallbackTimer.stop();
226+}
227+
228+void Greeter::setLanguage (const QString &language)
229+{
230+ Q_UNUSED(language)
231+}
232+
233+bool Greeter::startSessionSync(const QString &session)
234+{
235+ Q_UNUSED(session)
236+
237+ // Send a request to hide the greeter. This is normally done by logind,
238+ // but when testing, we don't want the bother of mocking that out. Instead,
239+ // just send the request directly ourselves.
240+ QDBusInterface iface("com.canonical.UnityGreeter",
241+ "/",
242+ "com.canonical.UnityGreeter",
243+ QDBusConnection::sessionBus());
244+ iface.asyncCall("HideGreeter");
245+
246+ return true;
247+}
248+
249+void Greeter::respond(const QString &response)
250+{
251+ Q_D(Greeter);
252+
253+ if (d->authenticationUser.isEmpty()) {
254+ // A manual login, our first question was which username
255+ d->authenticationUser = response;
256+ handleAuthenticate();
257+ return;
258+ }
259+
260+ if (d->authenticationUser == "no-response") {
261+ return;
262+ } else if (d->authenticationUser == "locked") {
263+ Q_EMIT showMessage("Account is locked", Greeter::MessageTypeError);
264+ sendAuthenticationComplete();
265+ return;
266+ } else if (d->authenticationUser == "two-factor") {
267+ if (!d->twoFactorDone) {
268+ if (response == "password") {
269+ d->twoFactorDone = true;
270+ Q_EMIT showPrompt("otp", Greeter::PromptTypeQuestion);
271+ } else {
272+ d->authenticated = false;
273+ sendAuthenticationComplete();
274+ }
275+ } else {
276+ d->authenticated = (response == "otp");
277+ sendAuthenticationComplete();
278+ }
279+ return;
280+ } else if (d->authenticationUser == "two-prompts" || d->authenticationUser == "wacky-prompts") {
281+ if (!d->twoFactorDone) {
282+ d->authenticated = (response == "blue");
283+ d->twoFactorDone = true;
284+ } else {
285+ d->authenticated = d->authenticated && (response == "password");
286+ sendAuthenticationComplete();
287+ }
288+ return;
289+ }
290+
291+ if (d->authenticationUser == "has-pin") {
292+ d->authenticated = (response == "1234");
293+ } else if (d->authenticationUser == "question-prompt") {
294+ d->authenticated = (response == "blue");
295+ } else {
296+ d->authenticated = (response == "password");
297+ }
298+
299+ if (d->authenticationUser == "info-after-login" && d->authenticated) {
300+ Q_EMIT showMessage("Congratulations on logging in!", Greeter::MessageTypeInfo);
301+ }
302+
303+ sendAuthenticationComplete();
304+}
305+
306+void Greeter::sendAuthenticationComplete()
307+{
308+ if (qEnvironmentVariableIsEmpty("UNITY_TESTING")) {
309+ // simulate PAM's delay
310+ QTimer::singleShot(1000, this, &Greeter::authenticationComplete);
311+ } else {
312+ Q_EMIT authenticationComplete();
313+ }
314+}
315+
316+}
tests/mocks/LightDM/IntegratedLightDM/liblightdm/Greeter.h → tests/mocks/liblightdm/MockGreeter.hrenamed+8−21
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
3 * Copyright (C) 2010-2011 David Edmundson.
3 * Copyright (C) 2010-2011 David Edmundson.
4 * Copyright (C) 2010-2011 Robert Ancell
4 * Copyright (C) 2010-2011 Robert Ancell
5 *
5 *
@@ -18,16 +18,14 @@
18 * Author: David Edmundson <kde@davidedmundson.co.uk>
18 * Author: David Edmundson <kde@davidedmundson.co.uk>
19 */
19 */
20
20
21−#ifndef UNITY_MOCK_GREETER_H
21+#pragma once
22−#define UNITY_MOCK_GREETER_H
23−
24−#include <QtCore/QObject>
25−#include <QtCore/QVariant>
26
22
23+#include <QObject>
24+#include <QVariant>
27
25
28namespace QLightDM
26namespace QLightDM
29{
27{
30− class GreeterPrivate;
28+class GreeterPrivate;
31
29
32class Q_DECL_EXPORT Greeter : public QObject
30class Q_DECL_EXPORT Greeter : public QObject
33{
31{
@@ -42,9 +40,6 @@ class Q_DECL_EXPORT Greeter : public QObject
42 Q_PROPERTY(QString hostname READ hostname CONSTANT)
40 Q_PROPERTY(QString hostname READ hostname CONSTANT)
43 Q_PROPERTY(bool hasGuestAccount READ hasGuestAccountHint CONSTANT)
41 Q_PROPERTY(bool hasGuestAccount READ hasGuestAccountHint CONSTANT)
44
42
45− //Mock-only API for testing purposes
46− Q_PROPERTY(QString mockMode READ mockMode WRITE setMockMode NOTIFY mockModeChanged)
47−
48public:
43public:
49 enum PromptType {
44 enum PromptType {
50 PromptTypeQuestion,
45 PromptTypeQuestion,
@@ -68,7 +63,6 @@ public:
68 bool showRemoteLoginHint() const;
63 bool showRemoteLoginHint() const;
69 bool hasGuestAccountHint() const;
64 bool hasGuestAccountHint() const;
70 QString selectUserHint() const;
65 QString selectUserHint() const;
71− void setSelectUserHint(const QString &selectUserHint); // only in mock
72 bool selectGuestHint() const;
66 bool selectGuestHint() const;
73 QString autologinUserHint() const;
67 QString autologinUserHint() const;
74 bool autologinGuestHint() const;
68 bool autologinGuestHint() const;
@@ -79,9 +73,6 @@ public:
79 QString authenticationUser() const;
73 QString authenticationUser() const;
80 QString hostname() const;
74 QString hostname() const;
81
75
82− QString mockMode() const;
83− void setMockMode(QString mockMode);
84−
85public Q_SLOTS:
76public Q_SLOTS:
86 bool connectSync();
77 bool connectSync();
87 void authenticate(const QString &username=QString());
78 void authenticate(const QString &username=QString());
@@ -98,18 +89,14 @@ Q_SIGNALS:
98 void showPrompt(QString text, QLightDM::Greeter::PromptType type);
89 void showPrompt(QString text, QLightDM::Greeter::PromptType type);
99 void authenticationComplete();
90 void authenticationComplete();
100 void autologinTimerExpired();
91 void autologinTimerExpired();
101− void mockModeChanged(QString mode);
102−
103−protected:
104− void sendAuthenticationComplete();
105
92
106private Q_SLOTS:
93private Q_SLOTS:
107− void delayedAuthentication();
94+ void handleAuthenticate();
108
95
109private:
96private:
97+ void sendAuthenticationComplete();
98+
110 GreeterPrivate *d_ptr;
99 GreeterPrivate *d_ptr;
111 Q_DECLARE_PRIVATE(Greeter)
100 Q_DECLARE_PRIVATE(Greeter)
112};
101};
113}
102}
114−
115−#endif // UNITY_MOCK_GREETER_H
tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModel.cpp → tests/mocks/liblightdm/MockSessionsModel.cpprenamed+40−42
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2015-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -21,21 +21,35 @@
21// set our compatibility to Qt4 here too.
21// set our compatibility to Qt4 here too.
22#define QT_DISABLE_DEPRECATED_BEFORE QT_VERSION_CHECK(4, 0, 0)
22#define QT_DISABLE_DEPRECATED_BEFORE QT_VERSION_CHECK(4, 0, 0)
23
23
24−#include "SessionsModel.h"
24+#include "MockController.h"
25−#include "SessionsModelPrivate.h"
25+#include "MockSessionsModel.h"
26−#include <QtCore/QDir>
27−#include <QtCore/QString>
28
26
29namespace QLightDM
27namespace QLightDM
30{
28{
31
29
32−SessionsModel::SessionsModel(QObject* parent) :
33− QAbstractListModel(parent),
34− d_ptr(new SessionsModelPrivate(this))
30+class SessionsModelPrivate
35{
31{
32+public:
33+ QHash<int, QByteArray> roleNames;
34+ QList<MockController::SessionItem> sessionItems;
35+};
36+
37+SessionsModel::SessionsModel(QObject* parent)
38+ : QAbstractListModel(parent)
39+ , d_ptr(new SessionsModelPrivate)
40+{
41+ Q_D(SessionsModel);
42+
36− m_roleNames = QAbstractListModel::roleNames();
43+ d->roleNames = QAbstractListModel::roleNames();
37− m_roleNames[KeyRole] = "key";
44+ d->roleNames[KeyRole] = "key";
38− m_roleNames[TypeRole] = "type";
45+ d->roleNames[TypeRole] = "type";
46+ setRoleNames(d->roleNames);
47+
48+ connect(MockController::instance(), &MockController::sessionModeChanged,
49+ this, &SessionsModel::resetEntries);
50+ connect(MockController::instance(), &MockController::numSessionsChanged,
51+ this, &SessionsModel::resetEntries);
52+ resetEntries();
39}
53}
40
54
41SessionsModel::~SessionsModel()
55SessionsModel::~SessionsModel()
@@ -65,7 +79,9 @@ QVariant SessionsModel::data(const QModelIndex& index, int role) const
65
79
66QHash<int, QByteArray> SessionsModel::roleNames() const
80QHash<int, QByteArray> SessionsModel::roleNames() const
67{
81{
68− return m_roleNames;
82+ Q_D(const SessionsModel);
83+
84+ return d->roleNames;
69}
85}
70
86
71int SessionsModel::rowCount(const QModelIndex& parent) const
87int SessionsModel::rowCount(const QModelIndex& parent) const
@@ -79,42 +95,24 @@ int SessionsModel::rowCount(const QModelIndex& parent) const
79 }
95 }
80}
96}
81
97
82−int SessionsModel::numSessions() const
83−{
84− Q_D(const SessionsModel);
85− return d->numSessions;
86−}
87−
88−int SessionsModel::numAvailableSessions() const
89−{
90− Q_D(const SessionsModel);
91− return d->numAvailableSessions();
92−}
93−
94−QString SessionsModel::testScenario() const
95−{
96− Q_D(const SessionsModel);
97− return d->testScenario;
98−}
99−
100−void SessionsModel::setNumSessions(int numSessions)
98+void SessionsModel::resetEntries()
101{
99{
102 Q_D(SessionsModel);
100 Q_D(SessionsModel);
103
101
104− if (d->numSessions != numSessions) {
102+ beginResetModel();
105− d->numSessions = numSessions;
106− d->resetEntries();
107− }
108−}
109
103
110−void SessionsModel::setTestScenario(QString testScenario)
104+ QString sessionMode = MockController::instance()->sessionMode();
111−{
112− Q_D(SessionsModel);
113
105
114− if (d->testScenario != testScenario) {
106+ if (sessionMode == "full") {
115− d->testScenario = testScenario;
107+ d->sessionItems = MockController::instance()->fullSessionItems();
116− d->resetEntries();
108+ d->sessionItems = d->sessionItems.mid(0, MockController::instance()->numSessions());
109+ } else if (sessionMode == "single") {
110+ d->sessionItems = {MockController::instance()->fullSessionItems()[0]};
111+ } else {
112+ d->sessionItems = {};
117 }
113 }
114+
115+ endResetModel();
118}
116}
119
117
120} // namespace QLightDM
118} // namespace QLightDM
tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModel.h → tests/mocks/liblightdm/MockSessionsModel.hrenamed+9−17
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2015-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -15,11 +15,12 @@
15 *
15 *
16 */
16 */
17
17
18−#ifndef UNITY_MOCK_SESSIONSMODEL_H
18+#pragma once
19−#define UNITY_MOCK_SESSIONSMODEL_H
20
19
21−#include <QtCore/QAbstractListModel>
20+#include <QAbstractListModel>
21+#include <QByteArray>
22+#include <QHash>
22−#include <QtCore/QString>
23+#include <QString>
23
24
24namespace QLightDM
25namespace QLightDM
25{
26{
@@ -30,7 +31,6 @@ class Q_DECL_EXPORT SessionsModel : public QAbstractListModel
30 Q_OBJECT
31 Q_OBJECT
31
32
32 public:
33 public:
33−
34 enum SessionModelRoles {
34 enum SessionModelRoles {
35 //name is exposed as Qt::DisplayRole
35 //name is exposed as Qt::DisplayRole
36 //comment is exposed as Qt::TooltipRole
36 //comment is exposed as Qt::TooltipRole
@@ -54,19 +54,11 @@ class Q_DECL_EXPORT SessionsModel : public QAbstractListModel
54 int rowCount(const QModelIndex& parent) const override;
54 int rowCount(const QModelIndex& parent) const override;
55 QVariant data(const QModelIndex& index, int role) const override;
55 QVariant data(const QModelIndex& index, int role) const override;
56
56
57− int numSessions() const;
57+ private Q_SLOTS:
58− int numAvailableSessions() const;
58+ void resetEntries();
59− QString testScenario() const;
60− void setNumSessions(int numSessions);
61− void setTestScenario(QString testScenario);
62−
63− protected:
64− SessionsModelPrivate* const d_ptr;
65
59
66 private:
60 private:
67− QHash<int, QByteArray> m_roleNames;
61+ SessionsModelPrivate *d_ptr;
68 Q_DECLARE_PRIVATE(SessionsModel)
62 Q_DECLARE_PRIVATE(SessionsModel)
69 };
63 };
70}
64}
71−
72−#endif // UNITY_MOCK_SESSIONSMODEL_H
tests/mocks/liblightdm/MockUsersModel.cppnew file+219
@@ -0,0 +1,219 @@
1+/*
2+ * Copyright (C) 2013-2016 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+ * Author: Michael Terry <michael.terry@canonical.com>
17+ */
18+
19+// LightDM currently is Qt4 compatible, and so doesn't define setRoleNames.
20+// To use the same method of setting role name that it does, we
21+// set our compatibility to Qt4 here too.
22+#define QT_DISABLE_DEPRECATED_BEFORE QT_VERSION_CHECK(4, 0, 0)
23+
24+#include "MockController.h"
25+#include "MockUsersModel.h"
26+#include <QDir>
27+#include <QIcon>
28+
29+namespace QLightDM
30+{
31+
32+class Entry
33+{
34+public:
35+ QString username;
36+ QString real_name;
37+ QString background;
38+ QString layouts;
39+ bool is_active;
40+ bool has_messages;
41+ QString session;
42+ uid_t uid;
43+};
44+
45+class UsersModelPrivate
46+{
47+public:
48+ QList<Entry> entries;
49+};
50+
51+UsersModel::UsersModel(QObject *parent)
52+ : QAbstractListModel(parent)
53+ , d_ptr(new UsersModelPrivate)
54+{
55+ // Extend roleNames (we want to keep the "display" role)
56+ QHash<int, QByteArray> roles = roleNames();
57+ roles[NameRole] = "name";
58+ roles[RealNameRole] = "realName";
59+ roles[LoggedInRole] = "loggedIn";
60+ roles[BackgroundRole] = "background";
61+ roles[BackgroundPathRole] = "backgroundPath";
62+ roles[SessionRole] = "session";
63+ roles[HasMessagesRole] = "hasMessages";
64+ roles[ImagePathRole] = "imagePath";
65+ roles[UidRole] = "uid";
66+ setRoleNames(roles);
67+
68+ connect(MockController::instance(), &MockController::hasGuestAccountHintChanged,
69+ this, &UsersModel::resetEntries);
70+ connect(MockController::instance(), &MockController::showManualLoginHintChanged,
71+ this, &UsersModel::resetEntries);
72+ connect(MockController::instance(), &MockController::hideUsersHintChanged,
73+ this, &UsersModel::resetEntries);
74+ connect(MockController::instance(), &MockController::userModeChanged,
75+ this, &UsersModel::resetEntries);
76+ resetEntries();
77+}
78+
79+UsersModel::~UsersModel()
80+{
81+ delete d_ptr;
82+}
83+
84+void UsersModel::setCurrentSessionName(const QString &sessionName, const QString &username)
85+{
86+ Q_D(UsersModel);
87+
88+ int usersIndex = 0;
89+ for (auto &entry : d->entries) {
90+ if (username == entry.username) {
91+ entry.session = sessionName;
92+ break;
93+ }
94+ usersIndex++;
95+ }
96+
97+ dataChanged(index(usersIndex, 0), index(usersIndex, 0), QVector<int>(SessionRole));
98+}
99+
100+int UsersModel::rowCount(const QModelIndex &parent) const
101+{
102+ Q_D(const UsersModel);
103+
104+ if (parent.isValid()) {
105+ return 0;
106+ } else { // parent is root
107+ return d->entries.size();
108+ }
109+}
110+
111+QVariant UsersModel::data(const QModelIndex &index, int role) const
112+{
113+ Q_D(const UsersModel);
114+
115+ if (!index.isValid()) {
116+ return QVariant();
117+ }
118+
119+ int row = index.row();
120+ switch (role) {
121+ case Qt::DisplayRole:
122+ return d->entries[row].real_name;
123+ case Qt::DecorationRole:
124+ return QIcon();
125+ case UsersModel::NameRole:
126+ return d->entries[row].username;
127+ case UsersModel::RealNameRole:
128+ return d->entries[row].real_name;
129+ case UsersModel::SessionRole:
130+ return d->entries[row].session;
131+ case UsersModel::LoggedInRole:
132+ return d->entries[row].is_active;
133+ case UsersModel::BackgroundRole:
134+ return QPixmap(d->entries[row].background);
135+ case UsersModel::BackgroundPathRole:
136+ return d->entries[row].background;
137+ case UsersModel::HasMessagesRole:
138+ return d->entries[row].has_messages;
139+ case UsersModel::ImagePathRole:
140+ return "";
141+ case UsersModel::UidRole:
142+ return d->entries[row].uid;
143+ default:
144+ return QVariant();
145+ }
146+}
147+
148+void UsersModel::resetEntries()
149+{
150+ Q_D(UsersModel);
151+
152+ beginResetModel();
153+
154+ QString userMode = MockController::instance()->userMode();
155+
156+ if (userMode == "single") {
157+ d->entries = {{"no-password", "No Password", "", 0, false, false, "ubuntu", 0}};
158+ } else if (userMode == "single-passphrase") {
159+ d->entries = {{"has-password", "Has Password", "", 0, false, false, "ubuntu", 0}};
160+ } else if (userMode == "single-pin") {
161+ d->entries = {{"has-pin", "Has PIN", "", 0, false, false, "ubuntu", 0}};
162+ } else if (userMode == "full") {
163+ /*
164+ * Since the real model sorts these entries alphabetically, the model indices
165+ * don't line up correctly. The easiest way to fix this is to ensure that this
166+ * list of entries REMAINS IN ALPHABETICAL ORDER at all times.
167+ */
168+ d->entries = {
169+ { "active", "Active Account", 0, 0, true, false, "ubuntu", 0 },
170+ { "auth-error", "Auth Error", 0, 0, false, false, "ubuntu", 0 },
171+ { "black-background", "Black Background", "#000000", 0, false, false, "ubuntu", 0 },
172+ { "color-background", "Color Background", "#E95420", 0, false, false, "ubuntu", 0 },
173+ { "different-prompt", "Different Prompt", 0, 0, false, false, "ubuntu", 0 },
174+ { "empty-name", "", 0, 0, false, false, "ubuntu", 0 },
175+ { "has-password", "Has Password", 0, 0, false, false, "ubuntu", 0 },
176+ { "has-pin", "Has PIN", 0, 0, false, false, "ubuntu", 0 },
177+ { "html-info-prompt", "HTML Info Prompt", 0, 0, false, false, "ubuntu", 0 },
178+ { "info-after-login", "Info After Login", 0, 0, false, false, "ubuntu", 0 },
179+ { "info-prompt", "Info Prompt", 0, 0, false, false, "ubuntu", 0 },
180+ { "wacky-prompts", "Interspersed Prompts", 0, 0, false, false, "ubuntu", 0 },
181+ { "invalid-session", "Invalid Session", 0, 0, false, false, "invalid", 0 },
182+ { "locked", "Locked", 0, 0, false, false, "ubuntu", 0 },
183+ { "long-info-prompt", "Long Info Prompt", 0, 0, false, false, "ubuntu", 0 },
184+ { "long-name", "Long name (far far too long to fit, seriously this would never fit on the screen, you will never see this part of the name)", 0, 0, false, false, "ubuntu", 0 },
185+ { "multi-info-prompt", "Multi Info Prompt", 0, 0, false, false, "ubuntu", 0 },
186+ { "no-background", "No Background", "", 0, false, false, "ubuntu", 0 },
187+ { "no-password", "No Password", 0, 0, false, false, "ubuntu", 0 },
188+ { "no-response", "No Response", 0, 0, false, false, "ubuntu", 0 },
189+ { "question-prompt", "Question Prompt", 0, 0, false, false, "ubuntu", 0 },
190+ { "two-factor", "Two Factor", 0, 0, false, false, "ubuntu", 0 },
191+ { "two-prompts", "Two Prompts", 0, 0, false, false, "ubuntu", 0 },
192+ // white and black are a bit redundant, but useful for manually testing if UI is still readable
193+ { "white-background", "White Background", "#ffffff", 0, false, false, "ubuntu", 0 },
194+ { "wide-info-prompt", "Wide Info Prompt", 0, 0, false, false, "ubuntu", 0 },
195+ { "unicode", "가나다라마", 0, 0, false, false, "ubuntu", 0 }
196+ };
197+ }
198+
199+ // Assign uids in a loop, just to avoid having to muck with them when
200+ // adding or removing test users.
201+ for (int i = 0; i < d->entries.size(); i++) {
202+ d->entries[i].uid = i + 1;
203+ }
204+
205+ // Assign backgrounds
206+ QDir backgroundDir("/usr/share/backgrounds");
207+ QStringList backgrounds = backgroundDir.entryList(QDir::Files);
208+ if (!backgrounds.empty()) {
209+ for (int i = 0; i < d->entries.size(); i++) {
210+ if (d->entries[i].background.isNull()) {
211+ d->entries[i].background = backgroundDir.filePath(backgrounds[i % backgrounds.size()]);
212+ }
213+ }
214+ }
215+
216+ endResetModel();
217+}
218+
219+}
tests/mocks/LightDM/IntegratedLightDM/liblightdm/UsersModel.h → tests/mocks/liblightdm/MockUsersModel.hrenamed+8−16
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013-2014 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
3 * Copyright (C) 2010-2011 David Edmundson.
3 * Copyright (C) 2010-2011 David Edmundson.
4 *
4 *
5 * This program is free software; you can redistribute it and/or modify
5 * This program is free software; you can redistribute it and/or modify
@@ -15,13 +15,11 @@
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
16 */
17
17
18−#ifndef UNITY_MOCK_USERSMODEL_H
18+#pragma once
19−#define UNITY_MOCK_USERSMODEL_H
20
19
21−#include <QtCore/QString>
22−#include <QtCore/QSharedDataPointer>
23#include <QAbstractListModel>
20#include <QAbstractListModel>
24−
21+#include <QList>
22+#include <QString>
25
23
26namespace QLightDM
24namespace QLightDM
27{
25{
@@ -31,12 +29,6 @@ class Q_DECL_EXPORT UsersModel : public QAbstractListModel
31{
29{
32 Q_OBJECT
30 Q_OBJECT
33
31
34− //Mock-only API for testing purposes
35− Q_PROPERTY(QString mockMode READ mockMode WRITE setMockMode NOTIFY mockModeChanged)
36−
37−Q_SIGNALS:
38− void mockModeChanged(QString mode);
39−
40public:
32public:
41 explicit UsersModel(QObject *parent = 0);
33 explicit UsersModel(QObject *parent = 0);
42 virtual ~UsersModel();
34 virtual ~UsersModel();
@@ -56,8 +48,10 @@ public:
56 int rowCount(const QModelIndex &parent) const override;
48 int rowCount(const QModelIndex &parent) const override;
57 QVariant data(const QModelIndex &index, int role) const override;
49 QVariant data(const QModelIndex &index, int role) const override;
58
50
59− QString mockMode() const;
51+ void setCurrentSessionName(const QString &sessionName, const QString &username);
60− void setMockMode(QString mockMode);
52+
53+private Q_SLOTS:
54+ void resetEntries();
61
55
62private:
56private:
63 UsersModelPrivate * const d_ptr;
57 UsersModelPrivate * const d_ptr;
@@ -65,5 +59,3 @@ private:
65};
59};
66
60
67}
61}
68−
69−#endif // UNITY_MOCK_USERSMODEL_H
tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModelPrivate.cpp → tests/mocks/liblightdm/SessionsModelPrivate.cpprenamed

The content is the same.

tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModelPrivate.h → tests/mocks/liblightdm/SessionsModelPrivate.hrenamed

The content is the same.

tests/mocks/LightDM/IntegratedLightDM/liblightdm/UsersModelPrivate.cpp → tests/mocks/liblightdm/UsersModelPrivate.cpprenamed

The content is the same.

tests/mocks/LightDM/IntegratedLightDM/liblightdm/UsersModelPrivate.h → tests/mocks/liblightdm/UsersModelPrivate.hrenamed

The content is the same.

tests/plugins/LightDM/IntegratedLightDM/CMakeLists.txt+34−6
@@ -1,11 +1,13 @@
1include_directories(
1include_directories(
2 ${GLIB_INCLUDE_DIRS}
2 ${GLIB_INCLUDE_DIRS}
3 ${CMAKE_CURRENT_BINARY_DIR}
3 ${CMAKE_CURRENT_BINARY_DIR}
4+ ${CMAKE_SOURCE_DIR}/plugins/LightDM/IntegratedLightDM
4 )
5 )
5
6
6add_executable(GreeterDBusTestExec
7add_executable(GreeterDBusTestExec
7 dbus.cpp
8 dbus.cpp
8 ${CMAKE_SOURCE_DIR}/plugins/LightDM/Greeter.cpp
9 ${CMAKE_SOURCE_DIR}/plugins/LightDM/Greeter.cpp
10+ ${CMAKE_SOURCE_DIR}/plugins/LightDM/PromptsModel.cpp
9 )
11 )
10# This add_dependencies is needed since we're linking with with -L and -l below
12# This add_dependencies is needed since we're linking with with -L and -l below
11# make seems to work but ninja breaks without it
13# make seems to work but ninja breaks without it
@@ -16,7 +18,6 @@ target_link_libraries(GreeterDBusTestExec
16 )
18 )
17target_include_directories(GreeterDBusTestExec PUBLIC
19target_include_directories(GreeterDBusTestExec PUBLIC
18 ${CMAKE_SOURCE_DIR}/plugins/LightDM
20 ${CMAKE_SOURCE_DIR}/plugins/LightDM
19− ${CMAKE_SOURCE_DIR}/tests/mocks/LightDM/IntegratedLightDM
20 )
21 )
21set_target_properties(GreeterDBusTestExec PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${SHELL_INSTALL_QML}/mocks/IntegratedLightDM/liblightdm")
22set_target_properties(GreeterDBusTestExec PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${SHELL_INSTALL_QML}/mocks/IntegratedLightDM/liblightdm")
22install(TARGETS GreeterDBusTestExec
23install(TARGETS GreeterDBusTestExec
@@ -28,7 +29,7 @@ install(FILES greeter.qml
28add_unity8_uitest(GreeterDBus dbus-test-runner
29add_unity8_uitest(GreeterDBus dbus-test-runner
29 ARG_PREFIX "--parameter"
30 ARG_PREFIX "--parameter"
30 ARGS --task $<TARGET_FILE:GreeterDBusTestExec>
31 ARGS --task $<TARGET_FILE:GreeterDBusTestExec>
31− DEPENDS MockLightDM MockLightDM-qml
32+ DEPENDS MockLightDM
32 )
33 )
33
34
34add_executable(GreeterPamTestExec
35add_executable(GreeterPamTestExec
@@ -63,19 +64,20 @@ target_link_libraries(GreeterSessionsModelTestExec
63target_include_directories(GreeterSessionsModelTestExec PUBLIC
64target_include_directories(GreeterSessionsModelTestExec PUBLIC
64 ${CMAKE_SOURCE_DIR}/plugins/LightDM
65 ${CMAKE_SOURCE_DIR}/plugins/LightDM
65 ${CMAKE_SOURCE_DIR}/plugins/Utils
66 ${CMAKE_SOURCE_DIR}/plugins/Utils
66− ${CMAKE_SOURCE_DIR}/tests/mocks/LightDM/IntegratedLightDM
67+ ${CMAKE_SOURCE_DIR}/tests/mocks/liblightdm/
67 )
68 )
68install(TARGETS GreeterSessionsModelTestExec
69install(TARGETS GreeterSessionsModelTestExec
69 DESTINATION "${SHELL_PRIVATE_LIBDIR}/tests/plugins/LightDM/IntegratedLightDM"
70 DESTINATION "${SHELL_PRIVATE_LIBDIR}/tests/plugins/LightDM/IntegratedLightDM"
70 )
71 )
71add_unity8_uitest(GreeterSessions GreeterSessionsModelTestExec
72add_unity8_uitest(GreeterSessions GreeterSessionsModelTestExec
72− ENVIRONMENT LIBLIGHTDM_MOCK_MODE=full
73 DEPENDS MockLightDM
73 DEPENDS MockLightDM
74 )
74 )
75
75
76# UsersModelTest
76# UsersModelTest
77add_executable(GreeterUsersModelTestExec
77add_executable(GreeterUsersModelTestExec
78 usersmodel.cpp
78 usersmodel.cpp
79+ ${CMAKE_SOURCE_DIR}/plugins/LightDM/Greeter.cpp
80+ ${CMAKE_SOURCE_DIR}/plugins/LightDM/PromptsModel.cpp
79 ${CMAKE_SOURCE_DIR}/plugins/LightDM/UsersModel.cpp
81 ${CMAKE_SOURCE_DIR}/plugins/LightDM/UsersModel.cpp
80 ${CMAKE_SOURCE_DIR}/plugins/Utils/unitysortfilterproxymodelqml.cpp
82 ${CMAKE_SOURCE_DIR}/plugins/Utils/unitysortfilterproxymodelqml.cpp
81 )
83 )
@@ -89,17 +91,43 @@ target_link_libraries(GreeterUsersModelTestExec
89target_include_directories(GreeterUsersModelTestExec PUBLIC
91target_include_directories(GreeterUsersModelTestExec PUBLIC
90 ${CMAKE_SOURCE_DIR}/plugins/LightDM
92 ${CMAKE_SOURCE_DIR}/plugins/LightDM
91 ${CMAKE_SOURCE_DIR}/plugins/Utils
93 ${CMAKE_SOURCE_DIR}/plugins/Utils
92− ${CMAKE_SOURCE_DIR}/tests/mocks/LightDM/IntegratedLightDM
94+ ${CMAKE_SOURCE_DIR}/tests/mocks/liblightdm
93 )
95 )
94set_target_properties(GreeterUsersModelTestExec PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${SHELL_INSTALL_QML}/mocks/IntegratedLightDM/liblightdm")
96set_target_properties(GreeterUsersModelTestExec PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${SHELL_INSTALL_QML}/mocks/IntegratedLightDM/liblightdm")
95install(TARGETS GreeterUsersModelTestExec
97install(TARGETS GreeterUsersModelTestExec
96 DESTINATION "${SHELL_PRIVATE_LIBDIR}/tests/plugins/LightDM/IntegratedLightDM"
98 DESTINATION "${SHELL_PRIVATE_LIBDIR}/tests/plugins/LightDM/IntegratedLightDM"
97 )
99 )
98add_unity8_uitest(GreeterUsers GreeterUsersModelTestExec
100add_unity8_uitest(GreeterUsers GreeterUsersModelTestExec
99− ENVIRONMENT LIBLIGHTDM_MOCK_MODE=full
100 DEPENDS MockLightDM
101 DEPENDS MockLightDM
101 )
102 )
102
103
104+# PromptsModelTest
105+add_executable(GreeterPromptsModelTestExec
106+ promptsmodel.cpp
107+ ${CMAKE_SOURCE_DIR}/plugins/LightDM/Greeter.cpp
108+ ${CMAKE_SOURCE_DIR}/plugins/LightDM/PromptsModel.cpp
109+ ${CMAKE_SOURCE_DIR}/plugins/Utils/unitysortfilterproxymodelqml.cpp
110+ )
111+# This add_dependencies is needed since we're linking with with -L and -l below
112+# make seems to work but ninja breaks without it
113+add_dependencies(GreeterPromptsModelTestExec MockLightDM)
114+qt5_use_modules(GreeterPromptsModelTestExec Core Test)
115+target_link_libraries(GreeterPromptsModelTestExec
116+ MockLightDM
117+ )
118+target_include_directories(GreeterPromptsModelTestExec PUBLIC
119+ ${CMAKE_SOURCE_DIR}/plugins/LightDM
120+ ${CMAKE_SOURCE_DIR}/plugins/Utils
121+ ${CMAKE_SOURCE_DIR}/tests/mocks/liblightdm
122+ )
123+install(TARGETS GreeterPromptsModelTestExec
124+ DESTINATION "${SHELL_PRIVATE_LIBDIR}/tests/plugins/LightDM/IntegratedLightDM"
125+ )
126+add_unity8_uitest(GreeterPrompts GreeterPromptsModelTestExec
127+ DEPENDS MockLightDM
128+ )
129+
130+# IntegratedTest
103add_executable(GreeterIntegratedTestExec
131add_executable(GreeterIntegratedTestExec
104 integrated.cpp
132 integrated.cpp
105 )
133 )
tests/plugins/LightDM/IntegratedLightDM/dbus.cpp+7−11
@@ -82,6 +82,7 @@ private Q_SLOTS:
82 void cleanup()
82 void cleanup()
83 {
83 {
84 delete view;
84 delete view;
85+ delete greeter; // reset singleton
85 }
86 }
86
87
87 void testGetActiveEntry()
88 void testGetActiveEntry()
@@ -137,10 +138,10 @@ private Q_SLOTS:
137 {
138 {
138 QSignalSpy spy(this, &GreeterDBusTest::PropertiesChangedRelay);
139 QSignalSpy spy(this, &GreeterDBusTest::PropertiesChangedRelay);
139 greeter->authenticate("has-password");
140 greeter->authenticate("has-password");
140− spy.wait();
141
141
142− QVERIFY(spy.count() > 0);
142+ QTRY_COMPARE(spy.count(), 2); // EntryIsLocked then ActiveEntry
143+
143− QList<QVariant> arguments = spy.takeFirst();
144+ QList<QVariant> arguments = spy[1];
144 QVERIFY(arguments.at(0).toString() == "com.canonical.UnityGreeter.List");
145 QVERIFY(arguments.at(0).toString() == "com.canonical.UnityGreeter.List");
145 QVERIFY(arguments.at(1).toMap().contains("ActiveEntry"));
146 QVERIFY(arguments.at(1).toMap().contains("ActiveEntry"));
146 QVERIFY(arguments.at(1).toMap()["ActiveEntry"] == "has-password");
147 QVERIFY(arguments.at(1).toMap()["ActiveEntry"] == "has-password");
@@ -162,15 +163,10 @@ private Q_SLOTS:
162 QSignalSpy spy(this, &GreeterDBusTest::PropertiesChangedRelay);
163 QSignalSpy spy(this, &GreeterDBusTest::PropertiesChangedRelay);
163 greeter->authenticate("no-password");
164 greeter->authenticate("no-password");
164
165
165− // Two property changed signals will be emitted, one for the IsLocked
166+ // EntryIsLocked=true, ActiveEntry, then EntryIsLocked=false
166− // property, one for the ActiveEntry; the first will be IsLocked.
167− spy.wait();
168− if (spy.count() < 2) {
169− spy.wait();
170− }
171− QCOMPARE(spy.count(), 2);
167+ QTRY_COMPARE(spy.count(), 3);
172
168
173− QList<QVariant> arguments = spy.takeLast();
169+ QList<QVariant> arguments = spy[2];
174 QVERIFY(arguments.at(0).toString() == "com.canonical.UnityGreeter.List");
170 QVERIFY(arguments.at(0).toString() == "com.canonical.UnityGreeter.List");
175 QVERIFY(arguments.at(1).toMap().contains("EntryIsLocked"));
171 QVERIFY(arguments.at(1).toMap().contains("EntryIsLocked"));
176 QVERIFY(arguments.at(1).toMap()["EntryIsLocked"] == false);
172 QVERIFY(arguments.at(1).toMap()["EntryIsLocked"] == false);
tests/plugins/LightDM/IntegratedLightDM/greeter.qml+2−7
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright 2013 Canonical Ltd.
2+ * Copyright 2013,2015-2016 Canonical Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -15,13 +15,8 @@
15 */
15 */
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18−import LightDM.IntegratedLightDM 0.1 as LightDM
18+import LightDM.FullLightDM 0.1 as LightDM
19
19
20Item {
20Item {
21 property var greeter: LightDM.Greeter
21 property var greeter: LightDM.Greeter
22− Binding {
23− target: LightDM.Greeter
24− property: "mockMode"
25− value: "full"
26− }
27}
22}
tests/plugins/LightDM/IntegratedLightDM/promptsmodel.cppnew file+175
@@ -0,0 +1,175 @@
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 "Greeter.h"
18+#include "PromptsModel.h"
19+
20+#include <QtTest>
21+
22+class GreeterPromptsModelTest : public QObject
23+{
24+ Q_OBJECT
25+
26+private:
27+ class PromptInfo {
28+ public:
29+ PromptInfo(PromptsModel::PromptType t, const QString &p)
30+ : type(t), text(p) {}
31+ PromptsModel::PromptType type;
32+ QString text;
33+ };
34+
35+ void selectUser(const QString &user)
36+ {
37+ Greeter::instance()->authenticate(user);
38+ QCOMPARE(Greeter::instance()->authenticationUser(), user);
39+ }
40+
41+ void respond(const QString &response, bool authenticated)
42+ {
43+ Greeter::instance()->respond(response);
44+ QCOMPARE(Greeter::instance()->isAuthenticated(), authenticated);
45+ }
46+
47+ void comparePrompts(const QList<PromptInfo> &expected)
48+ {
49+ QTRY_COMPARE(prompts->rowCount(), expected.size());
50+ for (int i = 0; i < prompts->rowCount(); i++) {
51+ QCOMPARE(prompts->data(prompts->index(i, 0), PromptsModel::TypeRole).toInt(), (int)expected[i].type);
52+ QCOMPARE(prompts->data(prompts->index(i, 0), PromptsModel::TextRole).toString(), expected[i].text);
53+ }
54+ }
55+
56+private Q_SLOTS:
57+
58+ void initTestCase()
59+ {
60+ prompts = Greeter::instance()->promptsModel();
61+ QVERIFY(prompts);
62+ }
63+
64+ void init()
65+ {
66+ QCOMPARE(prompts->rowCount(), 0);
67+ }
68+
69+ void cleanup()
70+ {
71+ prompts->clear();
72+ }
73+
74+ void testSimpleFailure()
75+ {
76+ selectUser("has-password");
77+ comparePrompts({{PromptsModel::Secret, ""}});
78+ respond("nope", false);
79+ comparePrompts({{PromptsModel::Secret, ""}}); // prompts don't change immediately
80+ selectUser("has-password");
81+ comparePrompts({{PromptsModel::Error, "Invalid password, please try again"},
82+ {PromptsModel::Secret, ""}});
83+ }
84+
85+ void testSimpleSuccess()
86+ {
87+ selectUser("has-password");
88+ comparePrompts({{PromptsModel::Secret, ""}});
89+ respond("password", true);
90+ comparePrompts({{PromptsModel::Secret, ""}}); // prompts don't change
91+ }
92+
93+ void testHasPassword()
94+ {
95+ selectUser("has-password");
96+ comparePrompts({{PromptsModel::Secret, ""}});
97+ }
98+
99+ void testDifferentPrompt()
100+ {
101+ selectUser("different-prompt");
102+ comparePrompts({{PromptsModel::Secret, "Secret word"}});
103+ }
104+
105+ void testNoPassword()
106+ {
107+ selectUser("no-password");
108+ comparePrompts({{PromptsModel::Button, "Log In"}});
109+ }
110+
111+ void testAuthError()
112+ {
113+ selectUser("auth-error");
114+ comparePrompts({{PromptsModel::Error, "Failed to authenticate"},
115+ {PromptsModel::Button, "Retry"}});
116+ }
117+
118+ void testTwoFactor()
119+ {
120+ selectUser("two-factor");
121+ comparePrompts({{PromptsModel::Secret, ""}});
122+ respond("password", false);
123+ comparePrompts({{PromptsModel::Question, "otp"}});
124+ }
125+
126+ void testTwoPrompts()
127+ {
128+ selectUser("two-prompts");
129+ comparePrompts({{PromptsModel::Question, "Favorite Color (blue)"},
130+ {PromptsModel::Secret, ""}});
131+ }
132+
133+ void testWackyPrompts()
134+ {
135+ selectUser("wacky-prompts");
136+ comparePrompts({{PromptsModel::Message, "First message"},
137+ {PromptsModel::Question, "Favorite Color (blue)"},
138+ {PromptsModel::Error, "Second message"},
139+ {PromptsModel::Secret, ""},
140+ {PromptsModel::Message, "Last message"}});
141+ }
142+
143+ void testHtmlInfoPrompts()
144+ {
145+ selectUser("html-info-prompt");
146+ comparePrompts({{PromptsModel::Message, "<b>&</b>"},
147+ {PromptsModel::Secret, ""}});
148+ }
149+
150+ void testInfoAfterLogin()
151+ {
152+ selectUser("info-after-login");
153+ comparePrompts({{PromptsModel::Secret, ""}});
154+ respond("password", true);
155+ comparePrompts({{PromptsModel::Message, "Congratulations on logging in!"},
156+ {PromptsModel::Button, "Log In"}});
157+ }
158+
159+ void testLocked()
160+ {
161+ selectUser("locked");
162+ comparePrompts({{PromptsModel::Secret, ""}});
163+ respond("nope", false);
164+ selectUser("locked");
165+ comparePrompts({{PromptsModel::Error, "Account is locked"},
166+ {PromptsModel::Secret, ""}});
167+ }
168+
169+private:
170+ PromptsModel *prompts;
171+};
172+
173+QTEST_GUILESS_MAIN(GreeterPromptsModelTest)
174+
175+#include "promptsmodel.moc"
tests/plugins/LightDM/IntegratedLightDM/sessionsmodel.cpp+15−13
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2015-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -16,6 +16,8 @@
16
16
17#include "SessionsModel.h"
17#include "SessionsModel.h"
18
18
19+#include "MockController.h"
20+
19#include <QLightDM/SessionsModel>
21#include <QLightDM/SessionsModel>
20#include <QtCore/QModelIndex>
22#include <QtCore/QModelIndex>
21#include <QtTest>
23#include <QtTest>
@@ -31,14 +33,14 @@ private Q_SLOTS:
31 {
33 {
32 model = new SessionsModel();
34 model = new SessionsModel();
33 QVERIFY(model);
35 QVERIFY(model);
34− sourceModel = new QLightDM::SessionsModel();
36+ mock = QLightDM::MockController::instance();
35− QVERIFY(sourceModel);
37+ QVERIFY(mock);
36 }
38 }
37
39
38 void cleanup()
40 void cleanup()
39 {
41 {
40 delete model;
42 delete model;
41− delete sourceModel;
43+ // mock will be a singleton, doesn't need to be cleaned
42 }
44 }
43
45
44 static QModelIndex findByKey(QAbstractItemModel *model, const QString& key)
46 static QModelIndex findByKey(QAbstractItemModel *model, const QString& key)
@@ -93,34 +95,34 @@ private Q_SLOTS:
93
95
94 void testMultipleSessionsCountIsCorrect()
96 void testMultipleSessionsCountIsCorrect()
95 {
97 {
96− sourceModel->setTestScenario("multipleSessions");
98+ mock->setProperty("sessionMode", "full");
97− QVERIFY(sourceModel->rowCount(QModelIndex()) > 1);
99+ QVERIFY(model->rowCount(QModelIndex()) > 1);
98 }
100 }
99
101
100 void testNoSessionsCountIsCorrect()
102 void testNoSessionsCountIsCorrect()
101 {
103 {
102− sourceModel->setTestScenario("noSessions");
104+ mock->setProperty("sessionMode", "none");
103− QVERIFY(sourceModel->rowCount(QModelIndex()) == 0);
105+ QVERIFY(model->rowCount(QModelIndex()) == 0);
104 }
106 }
105
107
106 void testSingleSessionCountIsCorrect()
108 void testSingleSessionCountIsCorrect()
107 {
109 {
108− sourceModel->setTestScenario("singleSession");
110+ mock->setProperty("sessionMode", "single");
109− QVERIFY(sourceModel->rowCount(QModelIndex()) == 1);
111+ QVERIFY(model->rowCount(QModelIndex()) == 1);
110 }
112 }
111
113
112 void testSessionNameIsCorrect()
114 void testSessionNameIsCorrect()
113 {
115 {
114 // This is testing the lookup, not the correctness of the strings,
116 // This is testing the lookup, not the correctness of the strings,
115 // so one test should be sufficient
117 // so one test should be sufficient
116− sourceModel->setTestScenario("multipleSessions");
118+ mock->setProperty("sessionMode", "full");
117− QVERIFY(model->data(findByKey(sourceModel, "ubuntu"),
119+ QVERIFY(model->data(findByKey(model, "ubuntu"),
118 Qt::DisplayRole).toString() == "Ubuntu");
120 Qt::DisplayRole).toString() == "Ubuntu");
119 }
121 }
120
122
121private:
123private:
122 SessionsModel *model;
124 SessionsModel *model;
123− QLightDM::SessionsModel *sourceModel;
125+ QObject *mock;
124};
126};
125
127
126QTEST_MAIN(GreeterSessionsModelTest)
128QTEST_MAIN(GreeterSessionsModelTest)
tests/plugins/LightDM/IntegratedLightDM/usersmodel.cpp+87−16
@@ -14,6 +14,8 @@
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 */
16
16
17+#include "Greeter.h"
18+#include "MockController.h"
17#include "UsersModel.h"
19#include "UsersModel.h"
18
20
19#include <QLightDM/UsersModel>
21#include <QLightDM/UsersModel>
@@ -40,43 +42,112 @@ private:
40 return model->data(model->index(i, 0), role).toString();
42 return model->data(model->index(i, 0), role).toString();
41 }
43 }
42
44
45+ void recreateModel()
46+ {
47+ delete model;
48+ model = new UsersModel();
49+ QVERIFY(model);
50+ }
51+
43private Q_SLOTS:
52private Q_SLOTS:
44
53
45 void init()
54 void init()
46 {
55 {
47− model = new UsersModel();
56+ recreateModel();
48− QVERIFY(model);
49− sourceModel = new QLightDM::UsersModel();
50− QVERIFY(sourceModel);
51 }
57 }
52
58
53 void cleanup()
59 void cleanup()
54 {
60 {
55− delete model;
61+ QLightDM::MockController::instance()->reset();
56− delete sourceModel;
57 }
62 }
58
63
59 void testMangleColor()
64 void testMangleColor()
60 {
65 {
61− QString background = getStringValue(sourceModel, "color-background", QLightDM::UsersModel::BackgroundPathRole);
62− QVERIFY(background == "#E95420");
63−
64− background = getStringValue(model, "color-background", QLightDM::UsersModel::BackgroundPathRole);
66+ auto background = getStringValue(model, "color-background", QLightDM::UsersModel::BackgroundPathRole);
65 QVERIFY(background == "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#E95420'/></svg>");
67 QVERIFY(background == "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#E95420'/></svg>");
66 }
68 }
67
69
68 void testMangleEmptyName()
70 void testMangleEmptyName()
69 {
71 {
70− QString name = getStringValue(sourceModel, "empty-name", QLightDM::UsersModel::RealNameRole);
71− QVERIFY(name == "");
72−
73− name = getStringValue(model, "empty-name", QLightDM::UsersModel::RealNameRole);
72+ auto name = getStringValue(model, "empty-name", QLightDM::UsersModel::RealNameRole);
74 QVERIFY(name == "empty-name");
73 QVERIFY(name == "empty-name");
75 }
74 }
76
75
76+ void testHasGuest()
77+ {
78+ // sanity check that it doesn't start already present
79+ QCOMPARE(findName(model, QStringLiteral("*guest")), -1);
80+
81+ QLightDM::MockController::instance()->setHasGuestAccountHint(true);
82+
83+ int i = findName(model, QStringLiteral("*guest"));
84+ QVERIFY(i >= 0);
85+
86+ auto realName = model->data(i, QLightDM::UsersModel::RealNameRole).toString();
87+ QCOMPARE(realName, QStringLiteral("Guest Session"));
88+
89+ auto loggedIn = model->data(i, QLightDM::UsersModel::LoggedInRole).toBool();
90+ QVERIFY(!loggedIn);
91+
92+ auto session = model->data(i, QLightDM::UsersModel::SessionRole).toString();
93+ QCOMPARE(session, Greeter::instance()->defaultSessionHint());
94+ }
95+
96+ void testHasManual()
97+ {
98+ // sanity check that it doesn't start already present
99+ QCOMPARE(findName(model, QStringLiteral("*other")), -1);
100+
101+ QLightDM::MockController::instance()->setShowManualLoginHint(true);
102+
103+ int i = findName(model, QStringLiteral("*other"));
104+ QVERIFY(i >= 0);
105+
106+ auto realName = model->data(i, QLightDM::UsersModel::RealNameRole).toString();
107+ QCOMPARE(realName, QStringLiteral("Login"));
108+
109+ auto loggedIn = model->data(i, QLightDM::UsersModel::LoggedInRole).toBool();
110+ QVERIFY(!loggedIn);
111+
112+ auto session = model->data(i, QLightDM::UsersModel::SessionRole).toString();
113+ QCOMPARE(session, Greeter::instance()->defaultSessionHint());
114+ }
115+
116+ void testHideUsers()
117+ {
118+ QLightDM::MockController::instance()->setHideUsersHint(true);
119+
120+ QCOMPARE(model->count(), 1);
121+ auto name = model->data(0, QLightDM::UsersModel::NameRole).toString();
122+ QCOMPARE(name, QStringLiteral("*other"));
123+ }
124+
125+ void testHideUsersWithGuest()
126+ {
127+ QLightDM::MockController::instance()->setHideUsersHint(true);
128+ QLightDM::MockController::instance()->setHasGuestAccountHint(true);
129+
130+ QCOMPARE(model->count(), 1);
131+ auto name = model->data(0, QLightDM::UsersModel::NameRole).toString();
132+ QCOMPARE(name, QStringLiteral("*guest"));
133+ }
134+
135+ void testCustomEntriesAreLast()
136+ {
137+ QLightDM::MockController::instance()->setHasGuestAccountHint(true);
138+ QLightDM::MockController::instance()->setShowManualLoginHint(true);
139+
140+ QVERIFY(model->count() > 2);
141+
142+ int manualIndex = findName(model, QStringLiteral("*other"));
143+ QCOMPARE(manualIndex, model->count() - 2);
144+
145+ int guestIndex = findName(model, QStringLiteral("*guest"));
146+ QCOMPARE(guestIndex, model->count() - 1);
147+ }
148+
77private:
149private:
78− UsersModel *model;
150+ UsersModel *model = nullptr;
79− QLightDM::UsersModel *sourceModel;
80};
151};
81
152
82QTEST_MAIN(GreeterUsersModelTest)
153QTEST_MAIN(GreeterUsersModelTest)
tests/plugins/Utils/CMakeLists.txt+4−1
@@ -6,11 +6,14 @@ include_directories(
6
6
7add_definitions(-DCONFIG_TESTING)
7add_definitions(-DCONFIG_TESTING)
8
8
9+execute_process(COMMAND mktemp -d OUTPUT_VARIABLE TMPDIR OUTPUT_STRIP_TRAILING_WHITESPACE)
10+
9foreach(util_test
11foreach(util_test
10 QLimitProxyModel
12 QLimitProxyModel
11 UnitySortFilterProxyModel
13 UnitySortFilterProxyModel
12 WindowInputMonitor
14 WindowInputMonitor
13 DeviceConfigParser
15 DeviceConfigParser
16+ WindowStateStorage
14)
17)
15 add_executable(${util_test}TestExec ${util_test}Test.cpp ModelTest.cpp)
18 add_executable(${util_test}TestExec ${util_test}Test.cpp ModelTest.cpp)
16 qt5_use_modules(${util_test}TestExec Test Core Qml)
19 qt5_use_modules(${util_test}TestExec Test Core Qml)
@@ -19,7 +22,7 @@ foreach(util_test
19 DESTINATION "${SHELL_PRIVATE_LIBDIR}/tests/plugins/Utils"
22 DESTINATION "${SHELL_PRIVATE_LIBDIR}/tests/plugins/Utils"
20 )
23 )
21 add_unity8_unittest(${util_test} ${util_test}TestExec ADD_TEST
24 add_unity8_unittest(${util_test} ${util_test}TestExec ADD_TEST
22− ENVIRONMENT LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/plugins/Utils
25+ ENVIRONMENT LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/plugins/Utils HOME=${TMPDIR}
23 )
26 )
24
27
25endforeach()
28endforeach()
tests/plugins/Utils/WindowStateStorageTest.cppnew file+64
@@ -0,0 +1,64 @@
1+/*
2+ * Copyright (C) 2017 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 "windowstatestorage.h"
18+
19+#include <QTest>
20+#include <QStandardPaths>
21+
22+class WindowStateStorageTest : public QObject
23+{
24+ Q_OBJECT
25+
26+private Q_SLOTS:
27+ void initTestCase() {
28+ QStandardPaths::setTestModeEnabled(true);
29+ }
30+
31+ void init()
32+ {
33+ storage = new WindowStateStorage(this);
34+ }
35+
36+ void cleanup()
37+ {
38+ delete storage;
39+ }
40+
41+ void testSaveRestoreState() {
42+ const WindowStateStorage::WindowState state{WindowStateStorage::WindowState::WindowStateMaximizedTopLeft};
43+ storage->saveState(QTest::currentTestFunction(), state);
44+ QTRY_COMPARE(storage->getState(QTest::currentTestFunction(), WindowStateStorage::WindowState::WindowStateNormal), state);
45+ }
46+
47+ void testSaveRestoreGeometry() {
48+ const QRect geometry{10, 20, 30, 40};
49+ storage->saveGeometry(QTest::currentTestFunction(), geometry);
50+ QTRY_COMPARE(storage->getGeometry(QTest::currentTestFunction(), QRect()), geometry);
51+ }
52+
53+ void testSaveRestoreStage() {
54+ const int stage{1};
55+ storage->saveStage(QTest::currentTestFunction(), stage);
56+ QTRY_COMPARE(storage->getStage(QTest::currentTestFunction(), 0), stage);
57+ }
58+
59+private:
60+ WindowStateStorage * storage{nullptr};
61+};
62+
63+QTEST_GUILESS_MAIN(WindowStateStorageTest)
64+#include "WindowStateStorageTest.moc"
tests/qmltests/ApplicationMenuDataLoader.qml+13−5
@@ -10,6 +10,7 @@ Object {
10
10
11 Connections {
11 Connections {
12 id: sMgrHandler
12 id: sMgrHandler
13+ target: null
13 onSurfaceCreated: {
14 onSurfaceCreated: {
14 var fakeMenuPath = "/" + surface.persistentId.replace(/\W+/g, "");
15 var fakeMenuPath = "/" + surface.persistentId.replace(/\W+/g, "");
15
16
@@ -25,21 +26,28 @@ Object {
25 var data = [];
26 var data = [];
26 if (root === undefined) root = true;
27 if (root === undefined) root = true;
27
28
28− if (prefix === undefined) prefix = "menu"
29−
30 for (var i = 0; i < length; i++) {
29 for (var i = 0; i < length; i++) {
31
30
31+ var menuName = prefix;
32+ if (menuName === undefined) {
33+ var chars = Math.random() * 20;
34+ menuName = "";
35+ for (var x = 0; x < chars; x++) {
36+ menuName += String.fromCharCode((Math.random() * 26) + 65);
37+ }
38+ }
39+
32 var menuCode = String.fromCharCode(i+65);
40 var menuCode = String.fromCharCode(i+65);
33
41
34 var isSeparator = !root && separatorInterval > 0 && ((i+1) % separatorInterval == 0);
42 var isSeparator = !root && separatorInterval > 0 && ((i+1) % separatorInterval == 0);
35 var row = {
43 var row = {
36 "rowData": { // 1
44 "rowData": { // 1
37− "label": prefix + "&" + menuCode,
45+ "label": menuName + "&" + menuCode,
38 "sensitive": true,
46 "sensitive": true,
39 "isSeparator": isSeparator,
47 "isSeparator": isSeparator,
40 "icon": "",
48 "icon": "",
41 "ext": {},
49 "ext": {},
42− "action": prefix + menuCode,
50+ "action": menuName + menuCode,
43 "actionState": {},
51 "actionState": {},
44 "isCheck": false,
52 "isCheck": false,
45 "isRadio": false,
53 "isRadio": false,
@@ -49,7 +57,7 @@ Object {
49 }
57 }
50 var isSubmenu = root === undefined || root === true || (submenuInterval > 0 && ((i+1) % submenuInterval == 0));
58 var isSubmenu = root === undefined || root === true || (submenuInterval > 0 && ((i+1) % submenuInterval == 0));
51 if (isSubmenu && !isSeparator && depth > 1) {
59 if (isSubmenu && !isSeparator && depth > 1) {
52− row["submenu"] = generateTestData(length, depth-1, submenuInterval, separatorInterval, prefix + menuCode + ".", false);
60+ row["submenu"] = generateTestData(length, depth-1, submenuInterval, separatorInterval,prefix, false);
53 }
61 }
54 data[i] = row;
62 data[i] = row;
55 }
63 }
tests/qmltests/ApplicationMenus/tst_MenuBar.qml+18−5
@@ -65,7 +65,7 @@ Item {
65
65
66 unityMenuModel: UnityMenuModel {
66 unityMenuModel: UnityMenuModel {
67 id: menuBackend
67 id: menuBackend
68− modelData: appMenuData.generateTestData(7,5,2,3,"menu")
68+ modelData: appMenuData.generateTestData(17,5,2,3)
69 }
69 }
70 }
70 }
71 }
71 }
@@ -83,12 +83,12 @@ Item {
83
83
84 function init() {
84 function init() {
85 menuBar.dismiss();
85 menuBar.dismiss();
86− menuBackend.modelData = appMenuData.generateTestData(5,5,2,3,"menu")
86+ menuBackend.modelData = appMenuData.generateTestData(5,5,2,3)
87 activatedSpy.clear();
87 activatedSpy.clear();
88 }
88 }
89
89
90 function test_mouseNavigation() {
90 function test_mouseNavigation() {
91− menuBackend.modelData = appMenuData.generateTestData(3,3,0,0,"menu");
91+ menuBackend.modelData = appMenuData.generateTestData(3,3,0,0);
92 wait(50) // wait for row to build
92 wait(50) // wait for row to build
93 var priv = findInvisibleChild(menuBar, "d");
93 var priv = findInvisibleChild(menuBar, "d");
94
94
@@ -114,7 +114,7 @@ Item {
114 }
114 }
115
115
116 function test_keyboardNavigation_RightKeySelectsNextMenuItem(data) {
116 function test_keyboardNavigation_RightKeySelectsNextMenuItem(data) {
117− menuBackend.modelData = appMenuData.generateTestData(3,3,0,0,"menu");
117+ menuBackend.modelData = appMenuData.generateTestData(3,3,0,0);
118 var priv = findInvisibleChild(menuBar, "d");
118 var priv = findInvisibleChild(menuBar, "d");
119
119
120 var menuItem0 = findChild(menuBar, "menuBar-item0"); verify(menuItem0);
120 var menuItem0 = findChild(menuBar, "menuBar-item0"); verify(menuItem0);
@@ -139,7 +139,7 @@ Item {
139 }
139 }
140
140
141 function test_keyboardNavigation_LeftKeySelectsPreviousMenuItem(data) {
141 function test_keyboardNavigation_LeftKeySelectsPreviousMenuItem(data) {
142− menuBackend.modelData = appMenuData.generateTestData(3,3,0,0,"menu");
142+ menuBackend.modelData = appMenuData.generateTestData(3,3,0,0);
143 var priv = findInvisibleChild(menuBar, "d");
143 var priv = findInvisibleChild(menuBar, "d");
144
144
145 var menuItem0 = findChild(menuBar, "menuBar-item0"); verify(menuItem0);
145 var menuItem0 = findChild(menuBar, "menuBar-item0"); verify(menuItem0);
@@ -179,5 +179,18 @@ Item {
179 keyPress(data.tag, Qt.AltModifier, 100);
179 keyPress(data.tag, Qt.AltModifier, 100);
180 tryCompare(priv, "currentItem", menuItem);
180 tryCompare(priv, "currentItem", menuItem);
181 }
181 }
182+
183+ function test_openAppMenuShortcut() {
184+ var priv = findInvisibleChild(menuBar, "d");
185+
186+ var menuItem0 = findChild(menuBar, "menuBar-item0"); verify(menuItem0);
187+ menuItem0.enabled = false;
188+
189+ var menuItem1 = findChild(menuBar, "menuBar-item1"); verify(menuItem1);
190+ verify(priv.currentItem === null);
191+
192+ keyClick(Qt.Key_F10, Qt.AltModifier);
193+ compare(priv.currentItem, menuItem1, "First enabled item should be opened");
194+ }
182 }
195 }
183}
196}
tests/qmltests/ApplicationMenus/tst_MenuPopup.qml+148−37
@@ -1,4 +1,4 @@
1−/*
1+/*
2 * Copyright (C) 2016 Canonical, Ltd.
2 * Copyright (C) 2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
@@ -28,8 +28,8 @@ import ".."
28
28
29Item {
29Item {
30 id: root
30 id: root
31− width: Math.max(units.gu(100), page.width + units.gu(6))
31+ width: Math.max(units.gu(100), loader.width + units.gu(6))
32− height: Math.max(units.gu(50), page.height + units.gu(6))
32+ height: Math.max(units.gu(50), loader.height + units.gu(6))
33
33
34 Binding {
34 Binding {
35 target: MouseTouchAdaptor
35 target: MouseTouchAdaptor
@@ -39,25 +39,37 @@ Item {
39
39
40 ApplicationMenuDataLoader { id: appMenuData }
40 ApplicationMenuDataLoader { id: appMenuData }
41
41
42+ Loader {
43+ id: loader
42− MenuPopup {
44+ sourceComponent: MenuPopup {
43− id: page
45+ anchors {
46+ left: parent ? parent.left : undefined
47+ top: parent ? parent.top : undefined
48+ leftMargin: units.gu(3)
49+ topMargin: units.gu(3)
50+ }
44
51
45− anchors {
52+ unityMenuModel: UnityMenuModel {
46− left: parent.left
53+ modelData: [{
47− top: parent.top
54+ "rowData": {
48− leftMargin: units.gu(3)
55+ "label": "Short",
49− topMargin: units.gu(3)
56+ }}, {
57+ "rowData": {
58+ "label": "This is a long menu item which tests width",
59+ }}
60+ ]
50− }
61+ }
51
62
52− unityMenuModel: UnityMenuModel {
63+ Binding {
53− id: menuBackend
64+ target: activatedSpy
54− modelData: appMenuData.generateTestData(7,5,2,3,"menu")
65+ property: "target"
66+ value: unityMenuModel
67+ }
55 }
68 }
56 }
69 }
57
70
58 SignalSpy {
71 SignalSpy {
59 id: activatedSpy
72 id: activatedSpy
60− target: menuBackend
61 signalName: "activated"
73 signalName: "activated"
62 }
74 }
63
75
@@ -66,14 +78,20 @@ Item {
66 name: "MenuPopup"
78 name: "MenuPopup"
67 when: windowShown
79 when: windowShown
68
80
81+ property var menu: loader.status === Loader.Ready ? loader.item : null
82+
69 function init() {
83 function init() {
84+ loader.active = true;
70− page.show();
85+ menu.show();
71 }
86 }
72
87
73 function cleanup() {
88 function cleanup() {
74− page.reset();
89+ menu.reset();
75 wait(100); // let the page dismiss
90 wait(100); // let the page dismiss
76 activatedSpy.clear();
91 activatedSpy.clear();
92+
93+ loader.active = false;
94+ tryCompare(loader, "item", null);
77 }
95 }
78
96
79 // visit and verify that all the backend menus have been created
97 // visit and verify that all the backend menus have been created
@@ -90,6 +108,7 @@ Item {
90 // recurse into submenu
108 // recurse into submenu
91 var submenu = rows[i]["submenu"];
109 var submenu = rows[i]["submenu"];
92 if (submenu) {
110 if (submenu) {
111+ waitForRendering(menuItem);
93 mouseClick(menuItem, menuItem.width/2, menuItem.height/2);
112 mouseClick(menuItem, menuItem.width/2, menuItem.height/2);
94 tryCompare(menuPriv, "currentItem", menuItem);
113 tryCompare(menuPriv, "currentItem", menuItem);
95
114
@@ -113,15 +132,15 @@ Item {
113 }
132 }
114
133
115 function test_mouseNavigation(data) {
134 function test_mouseNavigation(data) {
116− menuBackend.modelData = data.testData;
135+ menu.unityMenuModel.modelData = data.testData;
117
136
118− recurseMenuConstruction(data.testData, page);
137+ recurseMenuConstruction(data.testData, menu);
119 }
138 }
120
139
121 function test_checkableMenuTogglesOnClick() {
140 function test_checkableMenuTogglesOnClick() {
122− menuBackend.modelData = appMenuData.singleCheckable;
141+ menu.unityMenuModel.modelData = appMenuData.singleCheckable;
123
142
124− var menuItem = findChild(page, "menu-item0-actionItem");
143+ var menuItem = findChild(menu, "menu-item0-actionItem");
125 verify(menuItem);
144 verify(menuItem);
126 compare(menuItem.action.checkable, true, "Menu item should be checkable");
145 compare(menuItem.action.checkable, true, "Menu item should be checkable");
127 compare(menuItem.action.checked, false, "Menu item should not be checked");
146 compare(menuItem.action.checked, false, "Menu item should not be checked");
@@ -132,13 +151,13 @@ Item {
132 }
151 }
133
152
134 function test_keyboardNavigation_DownKeySelectsAndOpensNextMenuItemAndRotates() {
153 function test_keyboardNavigation_DownKeySelectsAndOpensNextMenuItemAndRotates() {
135− menuBackend.modelData = appMenuData.generateTestData(3,3,0,0,"menu",false);
154+ menu.unityMenuModel.modelData = appMenuData.generateTestData(3,3,0,0,"menu",false);
136
155
137− var item0 = findChild(page, "menu-item0"); verify(item0);
156+ var item0 = findChild(menu, "menu-item0"); verify(item0);
138− var item1 = findChild(page, "menu-item1"); verify(item1);
157+ var item1 = findChild(menu, "menu-item1"); verify(item1);
139− var item2 = findChild(page, "menu-item2"); verify(item2);
158+ var item2 = findChild(menu, "menu-item2"); verify(item2);
140
159
141− var priv = findInvisibleChild(page, "d");
160+ var priv = findInvisibleChild(menu, "d");
142
161
143 keyClick(Qt.Key_Down, Qt.NoModifier);
162 keyClick(Qt.Key_Down, Qt.NoModifier);
144 compare(priv.currentItem, item0, "CurrentItem should have moved to item 0");
163 compare(priv.currentItem, item0, "CurrentItem should have moved to item 0");
@@ -154,13 +173,13 @@ Item {
154 }
173 }
155
174
156 function test_keyboardNavigation_UpKeySelectsAndOpensPreviousMenuItemAndRotates() {
175 function test_keyboardNavigation_UpKeySelectsAndOpensPreviousMenuItemAndRotates() {
157− menuBackend.modelData = appMenuData.generateTestData(3,3,0,0,"menu",false);
176+ menu.unityMenuModel.modelData = appMenuData.generateTestData(3,3,0,0,"menu",false);
158
177
159− var item0 = findChild(page, "menu-item0"); verify(item0);
178+ var item0 = findChild(menu, "menu-item0"); verify(item0);
160− var item1 = findChild(page, "menu-item1"); verify(item1);
179+ var item1 = findChild(menu, "menu-item1"); verify(item1);
161− var item2 = findChild(page, "menu-item2"); verify(item2);
180+ var item2 = findChild(menu, "menu-item2"); verify(item2);
162
181
163− var priv = findInvisibleChild(page, "d");
182+ var priv = findInvisibleChild(menu, "d");
164
183
165 keyClick(Qt.Key_Down, Qt.NoModifier);
184 keyClick(Qt.Key_Down, Qt.NoModifier);
166 compare(priv.currentItem, item0, "CurrentItem should have moved to item 2");
185 compare(priv.currentItem, item0, "CurrentItem should have moved to item 2");
@@ -176,17 +195,17 @@ Item {
176 }
195 }
177
196
178 function test_keyboardNavigation_RightKeyEntersSubMenu() {
197 function test_keyboardNavigation_RightKeyEntersSubMenu() {
179− menuBackend.modelData = appMenuData.generateTestData(3,3,1,0,"menu",false);
198+ menu.unityMenuModel.modelData = appMenuData.generateTestData(3,3,1,0,"menu",false);
180
199
181− var menuItem = findChild(page, "menu-item0");
200+ var menuItem = findChild(menu, "menu-item0");
182
201
183− var priv = findInvisibleChild(page, "d");
202+ var priv = findInvisibleChild(menu, "d");
184 priv.currentItem = menuItem;
203 priv.currentItem = menuItem;
185
204
186 keyClick(Qt.Key_Right, Qt.NoModifier);
205 keyClick(Qt.Key_Right, Qt.NoModifier);
187 tryCompareFunction(function() { return menuItem.popup !== null && menuItem.popup.visible }, true);
206 tryCompareFunction(function() { return menuItem.popup !== null && menuItem.popup.visible }, true);
188
207
189− var submenu0 = findChild(page, "menu-item0-menu"); verify(submenu0);
208+ var submenu0 = findChild(menu, "menu-item0-menu"); verify(submenu0);
190 var submenu0item0 = findChild(submenu0, "menu-item0-menu-item0"); verify(submenu0item0);
209 var submenu0item0 = findChild(submenu0, "menu-item0-menu-item0"); verify(submenu0item0);
191
210
192 var submenu0Priv = findInvisibleChild(submenu0, "d"); verify(submenu0Priv);
211 var submenu0Priv = findInvisibleChild(submenu0, "d"); verify(submenu0Priv);
@@ -194,14 +213,106 @@ Item {
194 }
213 }
195
214
196 function test_keyboardNavigation_LeftKeyClosesSubMenu() {
215 function test_keyboardNavigation_LeftKeyClosesSubMenu() {
197− menuBackend.modelData = appMenuData.generateTestData(3,3,1,0,"menu",false);
216+ menu.unityMenuModel.modelData = appMenuData.generateTestData(3,3,1,0,"menu",false);
198
217
199− var menuItem = findChild(page, "menu-item0"); verify(menuItem);
218+ var menuItem = findChild(menu, "menu-item0"); verify(menuItem);
200 mouseClick(menuItem, menuItem.width/2, menuItem.height/2);
219 mouseClick(menuItem, menuItem.width/2, menuItem.height/2);
201 tryCompareFunction(function() { return menuItem.popup !== null && menuItem.popup.visible }, true);
220 tryCompareFunction(function() { return menuItem.popup !== null && menuItem.popup.visible }, true);
202
221
203 keyClick(Qt.Key_Left, Qt.NoModifier);
222 keyClick(Qt.Key_Left, Qt.NoModifier);
204 tryCompareFunction(function() { return menuItem.popup !== null && menuItem.popup.visible }, false);
223 tryCompareFunction(function() { return menuItem.popup !== null && menuItem.popup.visible }, false);
205 }
224 }
225+
226+ function test_differentSizes() {
227+ var differentSizesMenu = [{
228+ "rowData": { "label": "Short" }}, {
229+ "rowData": { "label": "This is a long menu item which tests width" }
230+ }];
231+
232+ menu.unityMenuModel.modelData = differentSizesMenu;
233+
234+ waitForRendering(menu);
235+ var longWidth = menu.width;
236+ differentSizesMenu.pop();
237+ menu.unityMenuModel.modelData = differentSizesMenu;
238+
239+ waitForRendering(menu);
240+ verify(menu.width < longWidth);
241+ }
242+
243+ function test_minimumWidth() {
244+ var shortMenu = [{
245+ "rowData": { "label": "Short" }
246+ }];
247+ menu.unityMenuModel.modelData = shortMenu;
248+
249+ var priv = findInvisibleChild(menu, "d");
250+ priv.__minimumWidth = 0;
251+ priv.__maximumWidth = 1000;
252+ tryCompareFunction(function() { return menu.width > priv.__minimumWidth; }, true);
253+
254+ priv.__minimumWidth = 300;
255+ tryCompare(menu, "width", priv.__minimumWidth);
256+ }
257+
258+ function test_maximumWidth() {
259+ var longMenu = [{
260+ "rowData": { "label": "This is a long menu item which tests width" }
261+ }];
262+
263+ var priv = findInvisibleChild(menu, "d");
264+ priv.__minimumWidth = 0;
265+ priv.__maximumWidth = 100;
266+
267+ menu.unityMenuModel.modelData = longMenu;
268+ tryCompare(menu, "width", priv.__maximumWidth);
269+
270+ priv.__maximumWidth = 200;
271+ tryCompare(menu, "width", priv.__maximumWidth);
272+
273+ priv.__maximumWidth = 1200;
274+ tryCompareFunction(function() { return menu.width < priv.__maximumWidth; }, true);
275+ }
276+
277+ function test_minimumHeight() {
278+ var shortMenu = [{
279+ "rowData": { "label": "menu1" }
280+ }];
281+ menu.unityMenuModel.modelData = shortMenu;
282+
283+ var priv = findInvisibleChild(menu, "d");
284+ priv.__minimumHeight = 0;
285+ priv.__maximumHeight = 1000;
286+ tryCompareFunction(function() { return menu.height > priv.__minimumHeight; }, true);
287+
288+ priv.__minimumHeight = 300;
289+ tryCompare(menu, "height", priv.__minimumHeight);
290+ }
291+
292+ function test_maximumHeight() {
293+ var shortMenu = [{
294+ "rowData": { "label": "menu1" }}, {
295+ "rowData": { "label": "menu2" }}, {
296+ "rowData": { "label": "menu3" }}, {
297+ "rowData": { "label": "menu4" }}, {
298+ "rowData": { "label": "menu5" }}, {
299+ "rowData": { "label": "menu6" }}, {
300+ "rowData": { "label": "menu7" }}, {
301+ "rowData": { "label": "menu8" }}, {
302+ "rowData": { "label": "menu9" }
303+ }];
304+ menu.unityMenuModel.modelData = shortMenu;
305+
306+ var priv = findInvisibleChild(menu, "d");
307+ priv.__minimumHeight = 0;
308+ priv.__maximumHeight = 100;
309+ tryCompare(menu, "height", priv.__maximumHeight);
310+
311+ priv.__maximumHeight = 200;
312+ tryCompare(menu, "height", priv.__maximumHeight);
313+
314+ priv.__maximumHeight = 1200;
315+ tryCompareFunction(function() { return menu.height < priv.__maximumHeight; }, true);
316+ }
206 }
317 }
207}
318}
tests/qmltests/Dash/tst_DashShell.qml+8−40
@@ -20,12 +20,20 @@ import "../../../qml/Dash"
20import "../../../qml/"
20import "../../../qml/"
21import Ubuntu.Components 1.3
21import Ubuntu.Components 1.3
22import Unity.Test 0.1 as UT
22import Unity.Test 0.1 as UT
23+import LightDMController 0.1
24+import LightDM.FullLightDM 0.1 as LightDM
23
25
24Item {
26Item {
25 id: root
27 id: root
26 width: units.gu(80)
28 width: units.gu(80)
27 height: units.gu(80)
29 height: units.gu(80)
28
30
31+ Binding {
32+ target: LightDMController
33+ property: "userMode"
34+ value: "single"
35+ }
36+
29 Shell {
37 Shell {
30 id: shell
38 id: shell
31 width: parent.width / 2
39 width: parent.width / 2
@@ -72,45 +80,5 @@ Item {
72 true, 15000);
80 true, 15000);
73 waitForRendering(findChild(dash, "scopeLoader0").item);
81 waitForRendering(findChild(dash, "scopeLoader0").item);
74 }
82 }
75−
76− function test_setShellHome() {
77− var dashContentList = findChild(dash, "dashContentList");
78− var startX = dash.width - units.gu(1);
79− var startY = dash.height / 2;
80− var stopX = units.gu(1)
81− var stopY = startY;
82− waitForRendering(dashContentList);
83− touchFlick(dash, startX, startY, stopX, stopY);
84− touchFlick(dash, startX, startY, stopX, stopY);
85− compare(dashContentList.currentIndex, 2, "Could not flick to scope id 2");
86−
87− // Flick the greeter away
88− var startX = shell.width - units.gu(1);
89− var startY = shell.height / 2;
90− var stopX = units.gu(1)
91− var stopY = startY;
92− touchFlick(shell, startX, startY, stopX, stopY);
93−
94− var greeter = findChild(shell, "greeter");
95− tryCompare(greeter, "shown", false);
96−
97− // bring in the launcher
98− var startX = units.gu(1);
99− var startY = shell.height / 2;
100− var stopX = shell.width / 3;
101− var stopY = startY;
102− touchFlick(shell, startX, startY, stopX, stopY);
103−
104− var launcher = findChild(shell, "launcher");
105− var buttonShowDashHome = findChild(launcher, "buttonShowDashHome");
106− tryCompare(buttonShowDashHome, "enabled", true);
107− tryCompare(launcher, "visibleWidth", launcher.panelWidth);
108−
109− // Make sure that opening the launcher doesn't change the scope
110− compare(dashContentList.currentIndex, 2, "Opening the launcher changed the current scope");
111−
112− mouseClick(buttonShowDashHome);
113− tryCompare(dashContentList, "currentIndex", 0);
114− }
115 }
83 }
116}
84}
tests/qmltests/Greeter/TestView.qml+7−16
@@ -32,6 +32,7 @@ Item {
32 property bool alphanumeric
32 property bool alphanumeric
33 property var userModel
33 property var userModel
34 property var infographicModel
34 property var infographicModel
35+ property bool waiting
35 readonly property bool fullyShown: _fullyShown
36 readonly property bool fullyShown: _fullyShown
36 readonly property bool required: _required
37 readonly property bool required: _required
37 readonly property bool animating: _animating
38 readonly property bool animating: _animating
@@ -45,24 +46,14 @@ Item {
45 signal tease()
46 signal tease()
46 signal emergencyCall()
47 signal emergencyCall()
47
48
48− signal _showMessageCalled(string html)
49− signal _showPromptCalled(string text, bool isSecret, bool isDefaultPrompt)
50 signal _showLastChanceCalled()
49 signal _showLastChanceCalled()
51 signal _hideCalled()
50 signal _hideCalled()
52− signal _notifyAuthenticationSucceededCalled(bool showFakePassword)
51+ signal _showFakePasswordCalled()
53 signal _notifyAuthenticationFailedCalled()
52 signal _notifyAuthenticationFailedCalled()
54 signal _showErrorMessageCalled(string msg)
53 signal _showErrorMessageCalled(string msg)
55− signal _resetCalled(bool forceShow)
54+ signal _forceShowCalled()
56 signal _tryToUnlockCalled(bool toTheRight)
55 signal _tryToUnlockCalled(bool toTheRight)
57
56
58− function showMessage(html) {
59− _showMessageCalled(html);
60− }
61−
62− function showPrompt(text, isSecret, isDefaultPrompt) {
63− _showPromptCalled(text, isSecret, isDefaultPrompt);
64− }
65−
66 function showLastChance() {
57 function showLastChance() {
67 _showLastChanceCalled();
58 _showLastChanceCalled();
68 }
59 }
@@ -73,8 +64,8 @@ Item {
73 _fullyShown = false;
64 _fullyShown = false;
74 }
65 }
75
66
76− function notifyAuthenticationSucceeded(showFakePassword) {
67+ function showFakePassword() {
77− _notifyAuthenticationSucceededCalled(showFakePassword);
68+ _showFakePasswordCalled();
78 }
69 }
79
70
80 function notifyAuthenticationFailed() {
71 function notifyAuthenticationFailed() {
@@ -85,8 +76,8 @@ Item {
85 _showErrorMessageCalled(msg);
76 _showErrorMessageCalled(msg);
86 }
77 }
87
78
88− function reset(forceShow) {
79+ function forceShow() {
89− _resetCalled(forceShow);
80+ _forceShowCalled();
90 }
81 }
91
82
92 function tryToUnlock(toTheRight) {
83 function tryToUnlock(toTheRight) {
tests/qmltests/Greeter/tst_Greeter.qml+62−110
@@ -22,7 +22,8 @@ import Ubuntu.Components 1.3
22import AccountsService 0.1
22import AccountsService 0.1
23import Biometryd 0.0
23import Biometryd 0.0
24import GSettings 1.0
24import GSettings 1.0
25+import LightDMController 0.1
25−import LightDM.IntegratedLightDM 0.1 as LightDM // This is the mock
26+import LightDM.FullLightDM 0.1 as LightDM
26import Unity.Test 0.1 as UT
27import Unity.Test 0.1 as UT
27
28
28Item {
29Item {
@@ -32,9 +33,6 @@ Item {
32 property url defaultBackground: "/usr/share/backgrounds/warty-final-ubuntu.png"
33 property url defaultBackground: "/usr/share/backgrounds/warty-final-ubuntu.png"
33
34
34 Component.onCompleted: {
35 Component.onCompleted: {
35− // set the mock mode before loading
36− LightDM.Greeter.mockMode = "full";
37− LightDM.Users.mockMode = "full";
38 loader.active = true;
36 loader.active = true;
39 }
37 }
40
38
@@ -77,6 +75,12 @@ Item {
77 signalName: "activeChanged"
75 signalName: "activeChanged"
78 }
76 }
79
77
78+ SignalSpy {
79+ id: authStartedSpy
80+ target: LightDM.Greeter
81+ signalName: "authenticationStarted"
82+ }
83+
80 GSettings {
84 GSettings {
81 id: greeterSettings
85 id: greeterSettings
82 schema.id: "com.canonical.Unity8.Greeter"
86 schema.id: "com.canonical.Unity8.Greeter"
@@ -90,18 +94,6 @@ Item {
90 property Item greeter: loader.status === Loader.Ready ? loader.item : null
94 property Item greeter: loader.status === Loader.Ready ? loader.item : null
91 property Item view
95 property Item view
92
96
93− SignalSpy {
94− id: viewShowMessageSpy
95− target: testCase.view
96− signalName: "_showMessageCalled"
97− }
98−
99− SignalSpy {
100− id: viewShowPromptSpy
101− target: testCase.view
102− signalName: "_showPromptCalled"
103− }
104−
105 SignalSpy {
97 SignalSpy {
106 id: viewShowLastChanceSpy
98 id: viewShowLastChanceSpy
107 target: testCase.view
99 target: testCase.view
@@ -115,9 +107,9 @@ Item {
115 }
107 }
116
108
117 SignalSpy {
109 SignalSpy {
118− id: viewAuthenticationSucceededSpy
110+ id: viewShowFakePasswordSpy
119 target: testCase.view
111 target: testCase.view
120− signalName: "_notifyAuthenticationSucceededCalled"
112+ signalName: "_showFakePasswordCalled"
121 }
113 }
122
114
123 SignalSpy {
115 SignalSpy {
@@ -133,9 +125,9 @@ Item {
133 }
125 }
134
126
135 SignalSpy {
127 SignalSpy {
136− id: viewResetSpy
128+ id: viewForceShowSpy
137 target: testCase.view
129 target: testCase.view
138− signalName: "_resetCalled"
130+ signalName: "_forceShowCalled"
139 }
131 }
140
132
141 SignalSpy {
133 SignalSpy {
@@ -146,7 +138,7 @@ Item {
146
138
147 function init() {
139 function init() {
148 greeterSettings.lockedOutTime = 0;
140 greeterSettings.lockedOutTime = 0;
149− LightDM.Greeter.selectUser = "";
141+ LightDMController.reset();
150 greeter.failedLoginsDelayAttempts = 7;
142 greeter.failedLoginsDelayAttempts = 7;
151 greeter.failedLoginsDelayMinutes = 5;
143 greeter.failedLoginsDelayMinutes = 5;
152 teaseSpy.clear();
144 teaseSpy.clear();
@@ -155,16 +147,15 @@ Item {
155 Biometryd.available = true;
147 Biometryd.available = true;
156 AccountsService.enableFingerprintIdentification = true;
148 AccountsService.enableFingerprintIdentification = true;
157 AccountsService.failedFingerprintLogins = 0;
149 AccountsService.failedFingerprintLogins = 0;
158− viewShowMessageSpy.clear();
159− viewShowPromptSpy.clear();
160 viewShowLastChanceSpy.clear();
150 viewShowLastChanceSpy.clear();
161 viewHideSpy.clear();
151 viewHideSpy.clear();
162− viewAuthenticationSucceededSpy.clear();
152+ viewShowFakePasswordSpy.clear();
163 viewAuthenticationFailedSpy.clear();
153 viewAuthenticationFailedSpy.clear();
164 viewShowErrorMessageSpy.clear();
154 viewShowErrorMessageSpy.clear();
165− viewResetSpy.clear();
155+ viewForceShowSpy.clear();
166 viewTryToUnlockSpy.clear();
156 viewTryToUnlockSpy.clear();
167 resetLoader();
157 resetLoader();
158+ authStartedSpy.clear();
168 }
159 }
169
160
170 function resetLoader() {
161 function resetLoader() {
@@ -203,13 +194,15 @@ Item {
203 var i = getIndexOf(name);
194 var i = getIndexOf(name);
204 compare(view.currentIndex, i);
195 compare(view.currentIndex, i);
205 compare(AccountsService.user, name);
196 compare(AccountsService.user, name);
197+ if (name[0] === "*") // custom rows have no authenticationUser set
198+ compare(LightDM.Greeter.authenticationUser, "");
199+ else
206− compare(LightDM.Greeter.authenticationUser, name);
200+ compare(LightDM.Greeter.authenticationUser, name);
207 return i;
201 return i;
208 }
202 }
209
203
210 function verifyLoggedIn() {
204 function verifyLoggedIn() {
211 tryCompare(sessionStartedSpy, "count", 1);
205 tryCompare(sessionStartedSpy, "count", 1);
212− verify(viewAuthenticationSucceededSpy.count > 0);
213 compare(LightDM.Greeter.authenticated, true);
206 compare(LightDM.Greeter.authenticated, true);
214 compare(greeter.shown, false);
207 compare(greeter.shown, false);
215 }
208 }
@@ -224,107 +217,52 @@ Item {
224
217
225 function test_unlockPass() {
218 function test_unlockPass() {
226 selectUser("has-password");
219 selectUser("has-password");
227− tryCompare(viewShowPromptSpy, "count", 1);
228− compare(viewShowPromptSpy.signalArguments[0][0], "Password");
229− compare(viewShowPromptSpy.signalArguments[0][1], true);
230− compare(viewShowPromptSpy.signalArguments[0][2], true);
231−
232 view.responded("password");
220 view.responded("password");
233 verifyLoggedIn();
221 verifyLoggedIn();
234 }
222 }
235
223
236 function test_unlockFail() {
224 function test_unlockFail() {
237 selectUser("has-password");
225 selectUser("has-password");
238− tryCompare(viewShowPromptSpy, "count", 1);
226+ tryCompare(authStartedSpy, "count", 1);
239− compare(viewShowPromptSpy.signalArguments[0][0], "Password");
240− compare(viewShowPromptSpy.signalArguments[0][1], true);
241− compare(viewShowPromptSpy.signalArguments[0][2], true);
242
227
243 view.responded("wr0ng p4ssw0rd");
228 view.responded("wr0ng p4ssw0rd");
244 tryCompare(viewAuthenticationFailedSpy, "count", 1);
229 tryCompare(viewAuthenticationFailedSpy, "count", 1);
245
230
246− tryCompare(viewShowPromptSpy, "count", 2);
231+ tryCompare(authStartedSpy, "count", 2);
247− compare(viewShowPromptSpy.signalArguments[0][0], "Password");
248− compare(viewShowPromptSpy.signalArguments[0][1], true);
249− compare(viewShowPromptSpy.signalArguments[0][2], true);
250 }
232 }
251
233
252 function test_promptless() {
234 function test_promptless() {
253 selectUser("no-password");
235 selectUser("no-password");
254 tryCompare(view, "locked", false);
236 tryCompare(view, "locked", false);
255− compare(viewShowPromptSpy.count, 0);
256 compare(viewHideSpy.count, 0);
237 compare(viewHideSpy.count, 0);
257 }
238 }
258
239
259 function test_twoFactorPass() {
240 function test_twoFactorPass() {
260 selectUser("two-factor");
241 selectUser("two-factor");
261− tryCompare(viewShowPromptSpy, "count", 1);
262− compare(viewShowPromptSpy.signalArguments[0][0], "Password");
263− compare(viewShowPromptSpy.signalArguments[0][1], true);
264− compare(viewShowPromptSpy.signalArguments[0][2], true);
265−
266 view.responded("password");
242 view.responded("password");
267− tryCompare(viewShowPromptSpy, "count", 2);
268− compare(viewShowPromptSpy.signalArguments[1][0], "otp");
269− compare(viewShowPromptSpy.signalArguments[1][1], false);
270− compare(viewShowPromptSpy.signalArguments[1][2], false);
271−
272 view.responded("otp");
243 view.responded("otp");
273 verifyLoggedIn();
244 verifyLoggedIn();
274 }
245 }
275
246
276 function test_twoFactorFailOnFirst() {
247 function test_twoFactorFailOnFirst() {
277 selectUser("two-factor");
248 selectUser("two-factor");
278− tryCompare(viewShowPromptSpy, "count", 1);
249+ tryCompare(authStartedSpy, "count", 1);
279− compare(viewShowPromptSpy.signalArguments[0][0], "Password");
280− compare(viewShowPromptSpy.signalArguments[0][1], true);
281− compare(viewShowPromptSpy.signalArguments[0][2], true);
282
250
283 view.responded("wr0ng p4ssw0rd");
251 view.responded("wr0ng p4ssw0rd");
284 tryCompare(viewAuthenticationFailedSpy, "count", 1);
252 tryCompare(viewAuthenticationFailedSpy, "count", 1);
285
253
286− tryCompare(viewShowPromptSpy, "count", 2);
254+ tryCompare(authStartedSpy, "count", 2);
287− compare(viewShowPromptSpy.signalArguments[0][0], "Password");
288− compare(viewShowPromptSpy.signalArguments[0][1], true);
289− compare(viewShowPromptSpy.signalArguments[0][2], true);
290 }
255 }
291
256
292 function test_twoFactorFailOnSecond() {
257 function test_twoFactorFailOnSecond() {
293 selectUser("two-factor");
258 selectUser("two-factor");
294− tryCompare(viewShowPromptSpy, "count", 1);
259+ tryCompare(authStartedSpy, "count", 1);
295− compare(viewShowPromptSpy.signalArguments[0][0], "Password");
296− compare(viewShowPromptSpy.signalArguments[0][1], true);
297− compare(viewShowPromptSpy.signalArguments[0][2], true);
298
260
299 view.responded("password");
261 view.responded("password");
300− tryCompare(viewShowPromptSpy, "count", 2);
301− compare(viewShowPromptSpy.signalArguments[1][0], "otp");
302− compare(viewShowPromptSpy.signalArguments[1][1], false);
303− compare(viewShowPromptSpy.signalArguments[1][2], false);
304−
305 view.responded("wr0ng p4ssw0rd");
262 view.responded("wr0ng p4ssw0rd");
306 tryCompare(viewAuthenticationFailedSpy, "count", 1);
263 tryCompare(viewAuthenticationFailedSpy, "count", 1);
307
264
308− tryCompare(viewShowPromptSpy, "count", 3);
265+ tryCompare(authStartedSpy, "count", 2);
309− compare(viewShowPromptSpy.signalArguments[0][0], "Password");
310− compare(viewShowPromptSpy.signalArguments[0][1], true);
311− compare(viewShowPromptSpy.signalArguments[0][2], true);
312− }
313−
314− function test_htmlInfoPrompt() {
315− selectUser("html-info-prompt");
316− tryCompare(viewShowPromptSpy, "count", 1);
317− compare(viewShowMessageSpy.count, 1);
318− compare(viewShowMessageSpy.signalArguments[0][0], "&lt;b&gt;&amp;&lt;/b&gt;");
319− }
320−
321− function test_multiInfoPrompt() {
322− selectUser("multi-info-prompt");
323− tryCompare(viewShowPromptSpy, "count", 1);
324− compare(viewShowMessageSpy.count, 3);
325− compare(viewShowMessageSpy.signalArguments[0][0], "Welcome to Unity Greeter");
326− compare(viewShowMessageSpy.signalArguments[1][0], "<font color=\"#df382c\">This is an error</font>");
327− compare(viewShowMessageSpy.signalArguments[2][0], "You should have seen three messages");
328 }
266 }
329
267
330 function test_locked() {
268 function test_locked() {
@@ -434,18 +372,9 @@ Item {
434 compare(view.backgroundTopMargin, -5);
372 compare(view.backgroundTopMargin, -5);
435 }
373 }
436
374
437− function test_differentPrompt() {
438− selectUser("different-prompt");
439− tryCompare(viewShowPromptSpy, "count", 1);
440− compare(viewShowPromptSpy.signalArguments[0][0], "Secret word");
441− compare(viewShowPromptSpy.signalArguments[0][1], true);
442− compare(viewShowPromptSpy.signalArguments[0][2], false);
443− }
444−
445 function test_authError() {
375 function test_authError() {
446 selectUser("auth-error");
376 selectUser("auth-error");
447 tryCompare(viewAuthenticationFailedSpy, "count", 1);
377 tryCompare(viewAuthenticationFailedSpy, "count", 1);
448− compare(viewShowPromptSpy.count, 0);
449 compare(view.locked, true);
378 compare(view.locked, true);
450 }
379 }
451
380
@@ -463,7 +392,6 @@ Item {
463 greeter.failedLoginsDelayAttempts = 1;
392 greeter.failedLoginsDelayAttempts = 1;
464 greeter.failedLoginsDelayMinutes = 1;
393 greeter.failedLoginsDelayMinutes = 1;
465 selectUser("has-password");
394 selectUser("has-password");
466− tryCompare(viewShowPromptSpy, "count", 1);
467 compare(greeterSettings.lockedOutTime, 0);
395 compare(greeterSettings.lockedOutTime, 0);
468 view.responded("wr0ng p4ssw0rd");
396 view.responded("wr0ng p4ssw0rd");
469
397
@@ -497,7 +425,6 @@ Item {
497 greeter.failedLoginsDelayMinutes = 0.001; // make delay very short
425 greeter.failedLoginsDelayMinutes = 0.001; // make delay very short
498
426
499 selectUser("has-password");
427 selectUser("has-password");
500− tryCompare(viewShowPromptSpy, "count", 1);
501
428
502 compare(view.delayMinutes, 0);
429 compare(view.delayMinutes, 0);
503 view.responded("wr0ng p4ssw0rd");
430 view.responded("wr0ng p4ssw0rd");
@@ -557,37 +484,62 @@ Item {
557
484
558 function test_dbusShowGreeterFromShownState() {
485 function test_dbusShowGreeterFromShownState() {
559 selectUser("has-password");
486 selectUser("has-password");
560− compare(viewResetSpy.count, 1);
561− tryCompare(viewShowPromptSpy, "count", 1);
562−
563− viewResetSpy.clear();
564− viewShowPromptSpy.clear();
565
487
488+ compare(viewForceShowSpy.count, 0);
566 LightDM.Greeter.showGreeter();
489 LightDM.Greeter.showGreeter();
567− compare(viewResetSpy.count, 1);
490+ compare(viewForceShowSpy.count, 1);
568− compare(viewResetSpy.signalArguments[0][0], true);
569− tryCompare(viewShowPromptSpy, "count", 1);
570 }
491 }
571
492
572 function test_selectUserHint() {
493 function test_selectUserHint() {
573− LightDM.Greeter.selectUser = "info-prompt";
494+ LightDMController.selectUserHint = "info-prompt";
574 resetLoader();
495 resetLoader();
575 var i = verifySelected("info-prompt");
496 var i = verifySelected("info-prompt");
576 verify(i != 0); // sanity-check that info-prompt isn't default 0 answer
497 verify(i != 0); // sanity-check that info-prompt isn't default 0 answer
577 }
498 }
578
499
579 function test_selectUserHintUnset() {
500 function test_selectUserHintUnset() {
580− LightDM.Greeter.selectUser = "";
501+ LightDMController.selectUserHint = "";
581 resetLoader();
502 resetLoader();
582 verifySelected(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
503 verifySelected(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
583 }
504 }
584
505
585 function test_selectUserHintInvalid() {
506 function test_selectUserHintInvalid() {
586− LightDM.Greeter.selectUser = "not-a-real-user";
507+ LightDMController.selectUserHint = "not-a-real-user";
587 resetLoader();
508 resetLoader();
588 verifySelected(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
509 verifySelected(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
589 }
510 }
590
511
512+ function test_selectUserHintGuest() {
513+ LightDMController.hasGuestAccountHint = true;
514+ LightDMController.selectGuestHint = true;
515+ LightDMController.selectUserHint = "info-prompt";
516+ resetLoader();
517+ verifySelected("*guest");
518+ }
519+
520+ function test_selectUserHintGuestWithNoGuest() {
521+ LightDMController.selectGuestHint = true;
522+ LightDMController.selectUserHint = "info-prompt";
523+ resetLoader();
524+ verifySelected("info-prompt");
525+ }
526+
527+ function test_hasGuestAccountHint() {
528+ LightDMController.hasGuestAccountHint = true;
529+ resetLoader();
530+ var i = selectUser("*guest");
531+ compare(i, LightDM.Users.count - 1); // guest should be last
532+ verify(!view.locked);
533+ }
534+
535+ function test_showManualLoginHint() {
536+ LightDMController.showManualLoginHint = true;
537+ resetLoader();
538+ var i = selectUser("*other");
539+ compare(i, LightDM.Users.count - 1); // manual should be last
540+ verify(view.locked);
541+ }
542+
591 function test_fingerprintSuccess() {
543 function test_fingerprintSuccess() {
592 var index = selectUser("has-password");
544 var index = selectUser("has-password");
593 unlockAndShowGreeter(); // turn on lockscreen mode
545 unlockAndShowGreeter(); // turn on lockscreen mode
tests/qmltests/Greeter/tst_Infographics.qml+1−11
@@ -20,19 +20,9 @@ import ".."
20import "../../../qml/Greeter"
20import "../../../qml/Greeter"
21import Ubuntu.Components 1.3
21import Ubuntu.Components 1.3
22import Unity.Test 0.1 as UT
22import Unity.Test 0.1 as UT
23−import LightDM.IntegratedLightDM 0.1 as LightDM
23+import LightDM.FullLightDM 0.1 as LightDM
24
24
25Item {
25Item {
26− Binding {
27− target: LightDM.Greeter
28− property: "mockMode"
29− value: "full"
30− }
31− Binding {
32− target: LightDM.Users
33− property: "mockMode"
34− value: "full"
35− }
36 property variant infographicModel: LightDM.Infographic
26 property variant infographicModel: LightDM.Infographic
37
27
38 width: units.gu(120)
28 width: units.gu(120)
tests/qmltests/Greeter/tst_NarrowView.qml+20−43
@@ -18,7 +18,8 @@ import QtQuick 2.4
18import QtTest 1.0
18import QtTest 1.0
19import ".."
19import ".."
20import "../../../qml/Greeter"
20import "../../../qml/Greeter"
21+import LightDMController 0.1
21−import LightDM.IntegratedLightDM 0.1 as LightDM
22+import LightDM.FullLightDM 0.1 as LightDM
22import Ubuntu.Components 1.3
23import Ubuntu.Components 1.3
23import Ubuntu.Telephony 0.1 as Telephony
24import Ubuntu.Telephony 0.1 as Telephony
24import Unity.Test 0.1 as UT
25import Unity.Test 0.1 as UT
@@ -30,6 +31,12 @@ Item {
30
31
31 Component.onCompleted: theme.name = "Ubuntu.Components.Themes.SuruDark" // use the same theme as the real shell
32 Component.onCompleted: theme.name = "Ubuntu.Components.Themes.SuruDark" // use the same theme as the real shell
32
33
34+ Binding {
35+ target: LightDMController
36+ property: "userMode"
37+ value: "single"
38+ }
39+
33 Row {
40 Row {
34 anchors.fill: parent
41 anchors.fill: parent
35 Loader {
42 Loader {
@@ -72,28 +79,16 @@ Item {
72 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
79 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
73 spacing: units.gu(1)
80 spacing: units.gu(1)
74
81
75− Row {
76− Button {
77− text: "Show Last Chance"
78− onClicked: loader.item.showLastChance()
79− }
80− }
81 Row {
82 Row {
82 Button {
83 Button {
83 text: "Hide"
84 text: "Hide"
84 onClicked: loader.item.hide()
85 onClicked: loader.item.hide()
85 }
86 }
86 }
87 }
87− Row {
88− Button {
89− text: "Reset"
90− onClicked: loader.item.reset()
91− }
92− }
93 Row {
88 Row {
94 Button {
89 Button {
95 text: "Show Message"
90 text: "Show Message"
96− onClicked: loader.item.showMessage(messageField.text)
91+ onClicked: LightDMService.prompts.append(messageField.text, LightDMService.prompts.Message)
97 }
92 }
98 TextField {
93 TextField {
99 id: messageField
94 id: messageField
@@ -104,7 +99,7 @@ Item {
104 Row {
99 Row {
105 Button {
100 Button {
106 text: "Show Prompt"
101 text: "Show Prompt"
107− onClicked: loader.item.showPrompt(promptField.text, isSecretCheckBox.checked, isDefaultPromptCheckBox.checked)
102+ onClicked: LightDMService.prompts.append(promptField.text, isSecretCheckBox.checked ? LightDMService.prompts.Secret : LightDMService.prompts.Question)
108 }
103 }
109 TextField {
104 TextField {
110 id: promptField
105 id: promptField
@@ -117,36 +112,14 @@ Item {
117 Label {
112 Label {
118 text: "secret"
113 text: "secret"
119 }
114 }
120− CheckBox {
121− id: isDefaultPromptCheckBox
122− }
123− Label {
124− text: "default"
125− }
126 }
115 }
127 Row {
116 Row {
128 Button {
117 Button {
129− text: "Authenticated"
118+ text: "Notify Auth Failure"
130 onClicked: {
119 onClicked: {
131− if (successCheckBox.checked) {
132− loader.item.notifyAuthenticationSucceeded(fakePasswordCheckBox.checked);
133− } else {
134− loader.item.notifyAuthenticationFailed();
120+ loader.item.notifyAuthenticationFailed();
135− }
136 }
121 }
137 }
122 }
138− CheckBox {
139− id: successCheckBox
140− }
141− Label {
142− text: "success"
143− }
144− CheckBox {
145− id: fakePasswordCheckBox
146− }
147− Label {
148− text: "fake password"
149− }
150 }
123 }
151 Row {
124 Row {
152 Button {
125 Button {
@@ -211,6 +184,7 @@ Item {
211 Row {
184 Row {
212 CheckBox {
185 CheckBox {
213 id: alphanumericCheckBox
186 id: alphanumericCheckBox
187+ checked: true
214 }
188 }
215 Label {
189 Label {
216 text: "alphanumeric"
190 text: "alphanumeric"
@@ -252,7 +226,7 @@ Item {
252 Binding {
226 Binding {
253 target: LightDM.Infographic
227 target: LightDM.Infographic
254 property: "username"
228 property: "username"
255− value: "single"
229+ value: "has-password"
256 }
230 }
257
231
258 SignalSpy {
232 SignalSpy {
@@ -294,6 +268,9 @@ Item {
294 function init() {
268 function init() {
295 view.currentIndex = 0; // break binding with text field
269 view.currentIndex = 0; // break binding with text field
296
270
271+ LightDM.Greeter.authenticate("no-password");
272+ tryCompare(LightDMService.prompts, "count", 1);
273+
297 telepathyHelper.ready = true;
274 telepathyHelper.ready = true;
298 telepathyHelper.emergencyCallsAvailable = true;
275 telepathyHelper.emergencyCallsAvailable = true;
299 selectedSpy.clear();
276 selectedSpy.clear();
@@ -358,8 +335,9 @@ Item {
358 }
335 }
359
336
360 function test_respondedWithPin() {
337 function test_respondedWithPin() {
338+ LightDM.Greeter.authenticate("has-pin");
361 view.locked = true;
339 view.locked = true;
362− view.showPrompt("", true, true);
340+ view.alphanumeric = false;
363 swipeAwayCover();
341 swipeAwayCover();
364 typeString("1234");
342 typeString("1234");
365 compare(respondedSpy.count, 1);
343 compare(respondedSpy.count, 1);
@@ -367,9 +345,8 @@ Item {
367 }
345 }
368
346
369 function test_respondedWithPassphrase() {
347 function test_respondedWithPassphrase() {
348+ LightDM.Greeter.authenticate("has-password");
370 view.locked = true;
349 view.locked = true;
371− view.alphanumeric = true;
372− view.showPrompt("", true, true);
373 swipeAwayCover();
350 swipeAwayCover();
374 typeString("test");
351 typeString("test");
375 keyClick(Qt.Key_Enter);
352 keyClick(Qt.Key_Enter);
tests/qmltests/Greeter/tst_WideView.qml+125−121
@@ -18,7 +18,8 @@ import QtQuick 2.4
18import QtTest 1.0
18import QtTest 1.0
19import ".."
19import ".."
20import "../../../qml/Greeter"
20import "../../../qml/Greeter"
21+import LightDMController 0.1
21−import LightDM.IntegratedLightDM 0.1 as LightDM
22+import LightDM.FullLightDM 0.1 as LightDM
22import Ubuntu.Components 1.3
23import Ubuntu.Components 1.3
23import Unity.Test 0.1 as UT
24import Unity.Test 0.1 as UT
24
25
@@ -31,12 +32,6 @@ StyledItem {
31
32
32 theme.name: "Ubuntu.Components.Themes.SuruDark"
33 theme.name: "Ubuntu.Components.Themes.SuruDark"
33
34
34− Binding {
35− target: LightDM.Users
36− property: "mockMode"
37− value: "full"
38− }
39−
40 Row {
35 Row {
41 anchors.fill: parent
36 anchors.fill: parent
42 Loader {
37 Loader {
@@ -115,28 +110,16 @@ StyledItem {
115 Column {
110 Column {
116 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
111 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
117 spacing: units.gu(1)
112 spacing: units.gu(1)
118− Row {
119− Button {
120− text: "Show Last Chance"
121− onClicked: loader.item.showLastChance()
122− }
123− }
124 Row {
113 Row {
125 Button {
114 Button {
126 text: "Hide"
115 text: "Hide"
127 onClicked: loader.item.hide()
116 onClicked: loader.item.hide()
128 }
117 }
129 }
118 }
130− Row {
131− Button {
132− text: "Reset"
133− onClicked: loader.item.reset()
134− }
135− }
136 Row {
119 Row {
137 Button {
120 Button {
138 text: "Show Message"
121 text: "Show Message"
139− onClicked: loader.item.showMessage(messageField.text)
122+ onClicked: LightDMService.prompts.append(messageField.text, LightDMService.prompts.Message)
140 }
123 }
141 TextField {
124 TextField {
142 id: messageField
125 id: messageField
@@ -147,7 +130,7 @@ StyledItem {
147 Row {
130 Row {
148 Button {
131 Button {
149 text: "Show Prompt"
132 text: "Show Prompt"
150− onClicked: loader.item.showPrompt(promptField.text, isSecretCheckBox.checked, isDefaultPromptCheckBox.checked)
133+ onClicked: LightDMService.prompts.append(promptField.text, isSecretCheckBox.checked ? LightDMService.prompts.Secret : LightDMService.prompts.Question)
151 }
134 }
152 TextField {
135 TextField {
153 id: promptField
136 id: promptField
@@ -160,30 +143,14 @@ StyledItem {
160 Label {
143 Label {
161 text: "secret"
144 text: "secret"
162 }
145 }
163− CheckBox {
164− id: isDefaultPromptCheckBox
165− }
166− Label {
167− text: "default"
168− }
169 }
146 }
170 Row {
147 Row {
171 Button {
148 Button {
172− text: "Authenticated"
149+ text: "Notify Auth Failure"
173 onClicked: {
150 onClicked: {
174− if (successCheckBox.checked) {
175− loader.item.notifyAuthenticationSucceeded(false);
176− } else {
177− loader.item.notifyAuthenticationFailed();
151+ loader.item.notifyAuthenticationFailed();
178− }
179 }
152 }
180 }
153 }
181− CheckBox {
182− id: successCheckBox
183− }
184− Label {
185− text: "success"
186− }
187 }
154 }
188 Row {
155 Row {
189 Button {
156 Button {
@@ -271,15 +238,15 @@ StyledItem {
271 id: multipleSessionsCheckbox
238 id: multipleSessionsCheckbox
272 onClicked: {
239 onClicked: {
273 if (checked) {
240 if (checked) {
274− LightDM.Sessions.testScenario = "multipleSessions"
241+ LightDMController.sessionMode = "full";
275 } else {
242 } else {
276− LightDM.Sessions.testScenario = "singleSession"
243+ LightDMController.sessionMode = "single";
277 }
244 }
278 }
245 }
279 Connections {
246 Connections {
280− target: LightDM.Sessions
247+ target: LightDMController
281− onTestScenarioChanged: {
248+ onSessionModeChanged: {
282− if (LightDM.Sessions.testScenario === "multipleSessions") {
249+ if (LightDMController.sessionMode === "full") {
283 multipleSessionsCheckbox.checked = true;
250 multipleSessionsCheckbox.checked = true;
284 } else {
251 } else {
285 multipleSessionsCheckbox.checked = false;
252 multipleSessionsCheckbox.checked = false;
@@ -297,11 +264,11 @@ StyledItem {
297
264
298 width: units.gu(10)
265 width: units.gu(10)
299 minimumValue: 0
266 minimumValue: 0
300− maximumValue: LightDM.Sessions.numAvailableSessions
267+ maximumValue: LightDMController.numAvailableSessions
301− value: LightDM.Sessions.numSessions
268+ value: LightDMController.numSessions
302− visible: LightDM.Sessions.testScenario === "multipleSessions"
269+ visible: LightDMController.sessionMode === "full"
303 Binding {
270 Binding {
304− target: LightDM.Sessions
271+ target: LightDMController
305 property: "numSessions"
272 property: "numSessions"
306 value: numSessionsSlider.value
273 value: numSessionsSlider.value
307 }
274 }
@@ -364,11 +331,17 @@ StyledItem {
364
331
365 function init() {
332 function init() {
366 selectIndex(0); // break binding with text field
333 selectIndex(0); // break binding with text field
334+ tryCompare(LightDMService.prompts, "count", 1);
367 selectedSpy.clear();
335 selectedSpy.clear();
368 respondedSpy.clear();
336 respondedSpy.clear();
369 teaseSpy.clear();
337 teaseSpy.clear();
370 emergencySpy.clear();
338 emergencySpy.clear();
371− LightDM.Sessions.testScenario = "multipleSessions"
339+ LightDMController.reset();
340+ LightDM.Sessions.iconSearchDirectories = [testIconDirectory];
341+
342+ waitForRendering(view);
343+ var userList = findChild(view, "userList");
344+ tryCompare(userList, "movingInternally", false);
372 }
345 }
373
346
374 function cleanup() {
347 function cleanup() {
@@ -395,8 +368,14 @@ StyledItem {
395 }
368 }
396
369
397 function selectIndex(i) {
370 function selectIndex(i) {
398− view.currentIndex = i;
371+ var user = LightDM.Users.data(i, LightDM.UserRoles.NameRole);
372+ LightDM.Greeter.authenticate(user);
373+
399 var userList = findChild(view, "userList");
374 var userList = findChild(view, "userList");
375+ var promptList = findChild(view, "promptList");
376+
377+ view.currentIndex = i;
378+ tryCompare(promptList, "opacity", 1);
400 tryCompare(userList, "movingInternally", false);
379 tryCompare(userList, "movingInternally", false);
401 }
380 }
402
381
@@ -415,6 +394,19 @@ StyledItem {
415 waitForRendering(view);
394 waitForRendering(view);
416 }
395 }
417
396
397+ function test_changingSessionSticksToUser() {
398+ var loginList = findChild(view, "loginList");
399+
400+ selectUser("invalid-session");
401+ tryCompare(loginList, "currentSession", "invalid");
402+
403+ selectUser("has-password");
404+ tryCompare(loginList, "currentSession", "ubuntu");
405+
406+ selectUser("invalid-session")
407+ tryCompare(loginList, "currentSession", "invalid");
408+ }
409+
418 function test_tease_data() {
410 function test_tease_data() {
419 return [
411 return [
420 {tag: "locked", x: 0, offset: 0, count: 0, locked: true},
412 {tag: "locked", x: 0, offset: 0, count: 0, locked: true},
@@ -426,10 +418,6 @@ StyledItem {
426 }
418 }
427
419
428 function test_sessionIconsAreValid() {
420 function test_sessionIconsAreValid() {
429− LightDM.Sessions.testScenario = "multipleSessions"
430− var originalDirectories = LightDM.Sessions.iconSearchDirectories
431− LightDM.Sessions.iconSearchDirectories = [testIconDirectory]
432−
433 selectUser("has-password");
421 selectUser("has-password");
434
422
435 // Test the login list icon is valid
423 // Test the login list icon is valid
@@ -449,12 +437,10 @@ StyledItem {
449
437
450 function test_choosingNewSessionChangesLoginListIcon() {
438 function test_choosingNewSessionChangesLoginListIcon() {
451 // Ensure the default session is selected (Ubuntu)
439 // Ensure the default session is selected (Ubuntu)
452− loader.active = false;
440+ cleanup();
453− loader.active = true;
454
441
455 selectUser("has-password");
442 selectUser("has-password");
456
443
457− LightDM.Sessions.testScenario = "multipleSessions";
458 var sessionChooserButton = findChild(view, "sessionChooserButton");
444 var sessionChooserButton = findChild(view, "sessionChooserButton");
459 var icon = String(sessionChooserButton.icon);
445 var icon = String(sessionChooserButton.icon);
460 compare(icon.indexOf("ubuntu") > -1, true);
446 compare(icon.indexOf("ubuntu") > -1, true);
@@ -465,25 +451,25 @@ StyledItem {
465 var currentDelegate = findChild(view, delegateName);
451 var currentDelegate = findChild(view, delegateName);
466 var sessionKey = LightDM.Sessions.data(i,LightDM.SessionRoles.KeyRole);
452 var sessionKey = LightDM.Sessions.data(i,LightDM.SessionRoles.KeyRole);
467 if (sessionKey === "gnome-classic") {
453 if (sessionKey === "gnome-classic") {
454+ waitForRendering(currentDelegate);
468 tap(currentDelegate);
455 tap(currentDelegate);
469− var sessionChooserButton = findChild(view, "sessionChooserButton");
470 waitForRendering(sessionChooserButton);
456 waitForRendering(sessionChooserButton);
471− var icon = String(sessionChooserButton.icon);
472 break;
457 break;
473 }
458 }
474 }
459 }
475
460
461+ icon = String(sessionChooserButton.icon);
476 compare(icon.indexOf("gnome") > -1, true,
462 compare(icon.indexOf("gnome") > -1, true,
477 "Expected icon to contain gnome but it was " + icon);
463 "Expected icon to contain gnome but it was " + icon);
478 }
464 }
479
465
480 function test_noSessionsDoesntBreakView() {
466 function test_noSessionsDoesntBreakView() {
481− LightDM.Sessions.testScenario = "noSessions"
467+ LightDMController.sessionMode = "none";
482 compare(LightDM.Sessions.count, 0)
468 compare(LightDM.Sessions.count, 0)
483 }
469 }
484
470
485 function test_sessionIconNotShownWithOneSession() {
471 function test_sessionIconNotShownWithOneSession() {
486− LightDM.Sessions.testScenario = "singleSession"
472+ LightDMController.sessionMode = "single";
487 compare(LightDM.Sessions.count, 1);
473 compare(LightDM.Sessions.count, 1);
488
474
489 var sessionChooserButton = findChild(view, "sessionChooserButton");
475 var sessionChooserButton = findChild(view, "sessionChooserButton");
@@ -491,9 +477,6 @@ StyledItem {
491 }
477 }
492
478
493 function test_sessionIconNotShownWithActiveUser() {
479 function test_sessionIconNotShownWithActiveUser() {
494− LightDM.Sessions.testScenario = "multipleSessions";
495− compare(LightDM.Sessions.count > 1, true);
496−
497 selectUser("active");
480 selectUser("active");
498
481
499 var sessionChooserButton = findChild(view, "sessionChooserButton");
482 var sessionChooserButton = findChild(view, "sessionChooserButton");
@@ -501,9 +484,6 @@ StyledItem {
501 }
484 }
502
485
503 function test_sessionIconShownWithMultipleSessions() {
486 function test_sessionIconShownWithMultipleSessions() {
504− LightDM.Sessions.testScenario = "multipleSessions"
505− compare(LightDM.Sessions.count > 1, true);
506−
507 selectUser("has-password");
487 selectUser("has-password");
508
488
509 var sessionChooserButton = findChild(view, "sessionChooserButton");
489 var sessionChooserButton = findChild(view, "sessionChooserButton");
@@ -537,12 +517,9 @@ StyledItem {
537 }
517 }
538
518
539 function test_respondedWithPassword() {
519 function test_respondedWithPassword() {
540− view.locked = true;
520+ selectUser("has-password");
541− view.showPrompt("Prompt", true, false);
521+ var greeterPrompt = findChild(view, "greeterPrompt0");
542− var passwordInput = findChild(view, "passwordInput");
543− compare(passwordInput.text, "Prompt");
544− verify(passwordInput.isSecret);
522+ verify(greeterPrompt.isSecret);
545− tap(passwordInput);
546 typeString("password");
523 typeString("password");
547 keyClick(Qt.Key_Enter);
524 keyClick(Qt.Key_Enter);
548 compare(respondedSpy.count, 1);
525 compare(respondedSpy.count, 1);
@@ -550,12 +527,9 @@ StyledItem {
550 }
527 }
551
528
552 function test_respondedWithNonSecret() {
529 function test_respondedWithNonSecret() {
553− view.locked = true;
530+ selectUser("question-prompt");
554− view.showPrompt("otp", false, false);
531+ var greeterPrompt = findChild(view, "greeterPrompt0");
555− var passwordInput = findChild(view, "passwordInput");
556− compare(passwordInput.text, "otp");
557− verify(!passwordInput.isSecret);
532+ verify(!greeterPrompt.isSecret);
558− tap(passwordInput);
559 typeString("foo");
533 typeString("foo");
560 keyClick(Qt.Key_Enter);
534 keyClick(Qt.Key_Enter);
561 compare(respondedSpy.count, 1);
535 compare(respondedSpy.count, 1);
@@ -580,24 +554,47 @@ StyledItem {
580 tryCompare(view, "required", false);
554 tryCompare(view, "required", false);
581 }
555 }
582
556
583− function test_showMessage() {
557+ function test_infoPrompt() {
584− view.showMessage("Welcome to Unity Greeter");
558+ selectUser("info-prompt");
585− view.showMessage("<font color=\"#df382c\">This is an error</font>");
559+
586− view.showMessage("You should have seen three messages and this is a really long message too. wow so long much length");
587− var infoLabel = findChild(view, "infoLabel");
560+ var infoLabel = findChild(view, "infoLabel0");
588− 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");
561+ compare(infoLabel.text, "Welcome to Unity Greeter");
589− compare(infoLabel.textFormat, Text.StyledText);
562+ compare(infoLabel.textFormat, Text.PlainText);
563+
564+ verify(findChild(view, "greeterPrompt1") != null);
565+ }
566+
567+ function test_longInfoPrompt() {
568+ selectUser("long-info-prompt");
569+
570+ var infoLabel = findChild(view, "infoLabel0");
571+ compare(infoLabel.text, "Welcome to Unity Greeter\n\nWe like to annoy you with super ridiculously long messages.\nLike this one\n\nThis is the last line of a multiple line message.");
590 verify(infoLabel.contentWidth > infoLabel.width);
572 verify(infoLabel.contentWidth > infoLabel.width);
591− verify(infoLabel.opacity < 1);
573+
592− tryCompare(infoLabel, "opacity", 1);
574+ verify(findChild(view, "greeterPrompt1") != null);
575+ }
576+
577+ function test_multiInfoPrompt() {
578+ selectUser("multi-info-prompt");
579+
580+ var infoLabel0 = findChild(view, "infoLabel0");
581+ compare(infoLabel0.text, "Welcome to Unity Greeter");
582+
583+ var infoLabel1 = findChild(view, "infoLabel1");
584+ compare(infoLabel1.text, "This is an error");
585+ compare(infoLabel1.color, theme.palette.normal.negative);
586+
587+ var infoLabel2 = findChild(view, "infoLabel2");
588+ compare(infoLabel2.text, "You should have seen three messages");
589+
590+ verify(findChild(view, "greeterPrompt3") != null);
593 }
591 }
594
592
595 // Escape is used to reset the authentication, especially if PAM is unresponsive
593 // Escape is used to reset the authentication, especially if PAM is unresponsive
596 function test_escape() {
594 function test_escape() {
597− selectIndex(1);
595+ var index = selectUser("has-password");
598 selectedSpy.clear();
596 selectedSpy.clear();
599 view.locked = true;
597 view.locked = true;
600− view.showPrompt("Prompt", true, true);
601 var promptField = findChild(view, "promptField");
598 var promptField = findChild(view, "promptField");
602 tap(promptField);
599 tap(promptField);
603 verify(promptField.activeFocus);
600 verify(promptField.activeFocus);
@@ -611,9 +608,10 @@ StyledItem {
611 compare(selectedSpy.count, 0);
608 compare(selectedSpy.count, 0);
612 keyClick(Qt.Key_Escape);
609 keyClick(Qt.Key_Escape);
613 compare(selectedSpy.count, 1);
610 compare(selectedSpy.count, 1);
614− compare(selectedSpy.signalArguments[0][0], 1);
611+ compare(selectedSpy.signalArguments[0][0], index);
615
612
616− view.reset();
613+ selectIndex(index);
614+ var promptField = findChild(view, "promptField");
617 verify(promptField.activeFocus);
615 verify(promptField.activeFocus);
618 compare(promptField.opacity, 1);
616 compare(promptField.opacity, 1);
619 }
617 }
@@ -624,21 +622,22 @@ StyledItem {
624 tryCompare(label, "text", "가나다라마");
622 tryCompare(label, "text", "가나다라마");
625 }
623 }
626
624
627− function test_promptless() {
625+ function test_authError() {
628− var passwordInput = findChild(view, "passwordInput");
626+ var index = selectUser("auth-error");
629−
627+ var greeterPrompt = findChild(view, "greeterPrompt1"); // after error message
630 view.locked = true;
628 view.locked = true;
631− compare(passwordInput.text, "Retry");
629+ compare(greeterPrompt.text, "Retry");
632− tap(passwordInput);
630+ tap(greeterPrompt);
633 compare(respondedSpy.count, 0);
631 compare(respondedSpy.count, 0);
634 compare(selectedSpy.count, 1);
632 compare(selectedSpy.count, 1);
635− compare(selectedSpy.signalArguments[0][0], 0);
633+ compare(selectedSpy.signalArguments[0][0], index);
636− selectedSpy.clear();
634+ }
637
635
638− view.reset();
636+ function test_noPassword() {
639− view.locked = false;
637+ selectUser("no-password");
638+ var greeterPrompt = findChild(view, "greeterPrompt0");
640− compare(passwordInput.text, "Log In");
639+ compare(greeterPrompt.text, "Log In");
641− tap(passwordInput);
640+ tap(greeterPrompt);
642 compare(selectedSpy.count, 0);
641 compare(selectedSpy.count, 0);
643 compare(respondedSpy.count, 1);
642 compare(respondedSpy.count, 1);
644 compare(respondedSpy.signalArguments[0][0], "");
643 compare(respondedSpy.signalArguments[0][0], "");
@@ -670,8 +669,8 @@ StyledItem {
670 }
669 }
671
670
672 function test_passphrase() {
671 function test_passphrase() {
672+ var index = selectUser("has-password");
673 var promptField = findChild(view, "promptField");
673 var promptField = findChild(view, "promptField");
674− view.showPrompt("", true, true);
675
674
676 verify(view.alphanumeric);
675 verify(view.alphanumeric);
677 compare(promptField.inputMethodHints & Qt.ImhDigitsOnly, 0);
676 compare(promptField.inputMethodHints & Qt.ImhDigitsOnly, 0);
@@ -681,8 +680,8 @@ StyledItem {
681 }
680 }
682
681
683 function test_passcode() {
682 function test_passcode() {
683+ var index = selectUser("has-pin");
684 var promptField = findChild(view, "promptField");
684 var promptField = findChild(view, "promptField");
685− view.showPrompt("", true, true);
686
685
687 view.alphanumeric = false;
686 view.alphanumeric = false;
688 compare(promptField.inputMethodHints & Qt.ImhDigitsOnly, Qt.ImhDigitsOnly);
687 compare(promptField.inputMethodHints & Qt.ImhDigitsOnly, Qt.ImhDigitsOnly);
@@ -704,54 +703,59 @@ StyledItem {
704
703
705 function test_loginListMovement_data() {
704 function test_loginListMovement_data() {
706 return [
705 return [
707− {tag: "up", key: Qt.Key_Up, result: -1},
706+ {tag: "up", key: Qt.Key_Up, result: 1},
708− {tag: "down", key: Qt.Key_Down, result: 1},
707+ {tag: "down", key: Qt.Key_Down, result: 3},
709 ]
708 ]
710 }
709 }
711
710
712 function test_loginListMovement(data) {
711 function test_loginListMovement(data) {
712+ selectIndex(2);
713+ selectedSpy.clear();
714+
713 keyClick(data.key);
715 keyClick(data.key);
714 compare(selectedSpy.count, 1);
716 compare(selectedSpy.count, 1);
715 compare(selectedSpy.signalArguments[0][0], data.result);
717 compare(selectedSpy.signalArguments[0][0], data.result);
716 }
718 }
717
719
718 function test_focusStaysActive() {
720 function test_focusStaysActive() {
719− var promptField = findChild(view, "promptField");
721+ selectUser("no-password");
720− var promptButton = findChild(view, "promptButton");
722+ var greeterPrompt = findChild(view, "greeterPrompt0");
721−
722− verify(promptButton.activeFocus);
723+ verify(greeterPrompt.activeFocus);
723 keyClick(Qt.Key_Enter);
724 keyClick(Qt.Key_Enter);
724 compare(selectedSpy.count, 0);
725 compare(selectedSpy.count, 0);
725 compare(respondedSpy.count, 1);
726 compare(respondedSpy.count, 1);
726 compare(respondedSpy.signalArguments[0][0], "");
727 compare(respondedSpy.signalArguments[0][0], "");
727− verify(promptButton.activeFocus);
728+ verify(greeterPrompt.activeFocus);
728 keyClick(Qt.Key_Enter);
729 keyClick(Qt.Key_Enter);
729 compare(respondedSpy.count, 1);
730 compare(respondedSpy.count, 1);
730
731
731− view.showPrompt("", true, true);
732+ selectUser("has-password");
733+ var greeterPrompt = findChild(view, "greeterPrompt0");
732− verify(promptField.activeFocus);
734+ verify(greeterPrompt.activeFocus);
733 keyClick(Qt.Key_D);
735 keyClick(Qt.Key_D);
734 keyClick(Qt.Key_Enter);
736 keyClick(Qt.Key_Enter);
735 compare(selectedSpy.count, 0);
737 compare(selectedSpy.count, 0);
736 compare(respondedSpy.count, 2);
738 compare(respondedSpy.count, 2);
737 compare(respondedSpy.signalArguments[1][0], "d");
739 compare(respondedSpy.signalArguments[1][0], "d");
738− verify(promptField.activeFocus);
740+ verify(greeterPrompt.activeFocus);
739 keyClick(Qt.Key_Enter);
741 keyClick(Qt.Key_Enter);
740 compare(respondedSpy.count, 2);
742 compare(respondedSpy.count, 2);
741
743
742− view.reset();
744+ var index = selectUser("no-password");
745+ var greeterPrompt = findChild(view, "greeterPrompt0");
743 view.locked = true;
746 view.locked = true;
744− verify(promptButton.activeFocus);
747+ verify(greeterPrompt.activeFocus);
745 keyClick(Qt.Key_Enter);
748 keyClick(Qt.Key_Enter);
746 compare(respondedSpy.count, 2);
749 compare(respondedSpy.count, 2);
747 compare(selectedSpy.count, 1);
750 compare(selectedSpy.count, 1);
748− compare(selectedSpy.signalArguments[0][0], 0);
751+ compare(selectedSpy.signalArguments[0][0], index);
749− verify(promptButton.activeFocus);
752+ verify(greeterPrompt.activeFocus);
750 keyClick(Qt.Key_Enter);
753 keyClick(Qt.Key_Enter);
751 compare(selectedSpy.count, 1);
754 compare(selectedSpy.count, 1);
752
755
753− view.showPrompt("", true, true);
756+ selectUser("no-password");
757+ var greeterPrompt = findChild(view, "greeterPrompt0");
754− verify(promptField.activeFocus);
758+ verify(greeterPrompt.activeFocus);
755 }
759 }
756 }
760 }
757}
761}
tests/qmltests/Launcher/tst_Drawer.qml+58−7
@@ -26,6 +26,7 @@ import Unity.Test 0.1
26StyledItem {
26StyledItem {
27 id: root
27 id: root
28 theme.name: "Ubuntu.Components.Themes.SuruDark"
28 theme.name: "Ubuntu.Components.Themes.SuruDark"
29+ focus: true
29
30
30 width: units.gu(140)
31 width: units.gu(140)
31 height: units.gu(70)
32 height: units.gu(70)
@@ -108,6 +109,7 @@ StyledItem {
108 tryCompareFunction( function(){ return drawer.x === 0; }, true );
109 tryCompareFunction( function(){ return drawer.x === 0; }, true );
109 tryCompare(launcher, "state", "drawer");
110 tryCompare(launcher, "state", "drawer");
110 tryCompare(launcher, "drawerShown", true);
111 tryCompare(launcher, "drawerShown", true);
112+ return drawer;
111 }
113 }
112
114
113 function revealByEdgePush() {
115 function revealByEdgePush() {
@@ -134,6 +136,8 @@ StyledItem {
134 launcher.hide();
136 launcher.hide();
135 var drawer = findChild(launcher, "drawer");
137 var drawer = findChild(launcher, "drawer");
136 tryCompare(drawer, "x", -drawer.width);
138 tryCompare(drawer, "x", -drawer.width);
139+ var searchField = findChild(drawer, "searchField");
140+ searchField.text = "";
137 }
141 }
138
142
139 function test_revealByEdgeDrag() {
143 function test_revealByEdgeDrag() {
@@ -168,12 +172,10 @@ StyledItem {
168 function test_hideByDraggingDrawer(data) {
172 function test_hideByDraggingDrawer(data) {
169 launcher.lockedVisible = !data.autohide;
173 launcher.lockedVisible = !data.autohide;
170
174
171− dragDrawerIntoView();
175+ var drawer = dragDrawerIntoView();
172 waitForRendering(launcher);
176 waitForRendering(launcher);
173
177
174− var drawer = findChild(launcher, "drawer");
175−
176− mouseFlick(root, drawer.width - units.gu(1), drawer.height / 2, units.gu(2), drawer.height / 2, true, true);
178+ mouseFlick(root, drawer.width - units.gu(1), drawer.height / 2, units.gu(10), drawer.height / 2, true, true);
177
179
178 tryCompare(drawer.anchors, "rightMargin", 0);
180 tryCompare(drawer.anchors, "rightMargin", 0);
179 tryCompare(launcher, "state", data.endState);
181 tryCompare(launcher, "state", data.endState);
@@ -181,9 +183,7 @@ StyledItem {
181 }
183 }
182
184
183 function test_hideByClickingOutside() {
185 function test_hideByClickingOutside() {
184− dragDrawerIntoView();
186+ var drawer = dragDrawerIntoView();
185−
186− var drawer = findChild(launcher, "drawer");
187
187
188 mouseClick(root, drawer.width + units.gu(1), root.height / 2);
188 mouseClick(root, drawer.width + units.gu(1), root.height / 2);
189
189
@@ -240,6 +240,8 @@ StyledItem {
240 keyClick(Qt.Key_Enter);
240 keyClick(Qt.Key_Enter);
241
241
242 compare(launcher.lastSelectedApplication, "camera-app");
242 compare(launcher.lastSelectedApplication, "camera-app");
243+ waitForRendering(launcher);
244+ tryCompare(launcher, "drawerShown", false);
243 }
245 }
244
246
245 function test_dragDirectionOnLeftEdgeDrag_data() {
247 function test_dragDirectionOnLeftEdgeDrag_data() {
@@ -260,5 +262,54 @@ StyledItem {
260
262
261 tryCompare(launcher, "state", data.endState)
263 tryCompare(launcher, "state", data.endState)
262 }
264 }
265+
266+ function test_searchDirectly() {
267+ var drawer = dragDrawerIntoView();
268+ waitForRendering(launcher);
269+ waitUntilTransitionsEnd(launcher);
270+ tryCompare(drawer, "focus", true);
271+
272+ var searchField = findChild(drawer, "searchField");
273+ tryCompareFunction(function() { return !!searchField }, true);
274+ typeString("cam");
275+ tryCompareFunction(function() { return searchField.displayText }, "cam");
276+ }
277+
278+ function test_kbdNavigation() {
279+ launcher.openDrawer(true);
280+ waitForRendering(launcher);
281+ waitUntilTransitionsEnd(launcher);
282+ compare(launcher.lastSelectedApplication, "");
283+
284+ var drawer = findChild(launcher, "drawer");
285+ var searchField = findChild(drawer, "searchField");
286+ var sections = findChild(drawer, "drawerSections");
287+ var header = findChild(drawer, "headerFocusScope");
288+ var listLoader = findChild(drawer, "drawerListLoader");
289+
290+ tryCompare(searchField, "activeFocus", true);
291+ // for some reason, even when the searchField has activeFocus already,
292+ // it won't react on key down events if they're coming in too early...
293+ // let's repeat the press for a bit before giving up
294+ tryCompareFunction(function() {
295+ wait(10)
296+ keyClick(Qt.Key_Down);
297+ return sections.activeFocus;
298+ }, true)
299+
300+ keyClick(Qt.Key_Down);
301+ tryCompare(header, "activeFocus", true);
302+
303+ keyClick(Qt.Key_Down);
304+ tryCompare(listLoader, "activeFocus", true);
305+
306+ keyClick(Qt.Key_Down);
307+ keyClick(Qt.Key_Left);
308+ tryCompare(listLoader, "activeFocus", true);
309+
310+ keyClick(Qt.Key_Enter);
311+
312+ compare(launcher.lastSelectedApplication, "calendar-app");
313+ }
263 }
314 }
264}
315}
tests/qmltests/Launcher/tst_Launcher.qml+15−6
@@ -295,6 +295,8 @@ Rectangle {
295 compare(listView.snapMode, ListView.SnapToItem, "Snapping is not enabled");
295 compare(listView.snapMode, ListView.SnapToItem, "Snapping is not enabled");
296
296
297 removeTimeConstraintsFromSwipeAreas(root);
297 removeTimeConstraintsFromSwipeAreas(root);
298+
299+ waitUntilLauncherDisappears();
298 }
300 }
299
301
300 function dragLauncher() {
302 function dragLauncher() {
@@ -369,18 +371,17 @@ Rectangle {
369 function assertFocusOnIndex(index) {
371 function assertFocusOnIndex(index) {
370 var launcherPanel = findChild(launcher, "launcherPanel");
372 var launcherPanel = findChild(launcher, "launcherPanel");
371 var launcherListView = findChild(launcher, "launcherListView");
373 var launcherListView = findChild(launcher, "launcherListView");
372− var bfbFocusHighlight = findChild(launcher, "bfbFocusHighlight");
374+ var bfb = findChild(launcher, "buttonShowDashHome");
373
375
374 waitForRendering(launcher);
376 waitForRendering(launcher);
375 tryCompare(launcherPanel, "highlightIndex", index);
377 tryCompare(launcherPanel, "highlightIndex", index);
376− compare(bfbFocusHighlight.visible, index === -1);
378+ compare(bfb.highlighted, index === -1);
377 for (var i = 0; i < launcherListView.count; i++) {
379 for (var i = 0; i < launcherListView.count; i++) {
378 var item = findChild(launcher, "launcherDelegate" + i);
380 var item = findChild(launcher, "launcherDelegate" + i);
379 // Delegates might be destroyed when not visible. We can't check if they paint a focus highlight.
381 // Delegates might be destroyed when not visible. We can't check if they paint a focus highlight.
380 // Make sure the requested index does have focus. for the others, try best effort to check if they don't
382 // Make sure the requested index does have focus. for the others, try best effort to check if they don't
381 if (index === i || item) {
383 if (index === i || item) {
382− var focusRing = findChild(item, "focusRing")
383− tryCompare(focusRing, "visible", index === i);
384+ tryCompare(item, "highlighted", index === i);
384 }
385 }
385 }
386 }
386 }
387 }
@@ -1188,13 +1189,13 @@ Rectangle {
1188 }
1189 }
1189
1190
1190 function test_keyboardNavigation(data) {
1191 function test_keyboardNavigation(data) {
1191− var bfbFocusHighlight = findChild(launcher, "bfbFocusHighlight");
1192+ var bfb = findChild(launcher, "buttonShowDashHome");
1192 var quickList = findChild(launcher, "quickList");
1193 var quickList = findChild(launcher, "quickList");
1193 var launcherPanel = findChild(launcher, "launcherPanel");
1194 var launcherPanel = findChild(launcher, "launcherPanel");
1194 var launcherListView = findChild(launcher, "launcherListView");
1195 var launcherListView = findChild(launcher, "launcherListView");
1195 var last = launcherListView.count - 1;
1196 var last = launcherListView.count - 1;
1196
1197
1197− compare(bfbFocusHighlight.visible, false);
1198+ compare(bfb.highlighted, false);
1198 launcher.openForKeyboardNavigation();
1199 launcher.openForKeyboardNavigation();
1199 tryCompare(launcherPanel, "x", 0);
1200 tryCompare(launcherPanel, "x", 0);
1200 waitForRendering(launcher);
1201 waitForRendering(launcher);
@@ -1478,5 +1479,13 @@ Rectangle {
1478
1479
1479 launcher.available = true;
1480 launcher.available = true;
1480 }
1481 }
1482+
1483+ function test_hintOnSizeChange() {
1484+ var oldSize = launcher.panelWidth;
1485+ launcher.maxPanelX = -launcher.panelWidth;
1486+ launcher.panelWidth = oldSize + units.gu(2);
1487+ tryCompare(launcher, "maxPanelX", 0);
1488+ launcher.panelWidth = oldSize;
1489+ }
1481 }
1490 }
1482}
1491}
tests/qmltests/Panel/tst_Panel.qml+36
@@ -338,6 +338,8 @@ PanelTest {
338 // expose more of the panel, binding it to the selected indicator and opening it's menu.
338 // expose more of the panel, binding it to the selected indicator and opening it's menu.
339 // Tested from first Y pixel to check for swipe from offscreen.
339 // Tested from first Y pixel to check for swipe from offscreen.
340 function test_drag_indicator_item_down_shows_menu(data) {
340 function test_drag_indicator_item_down_shows_menu(data) {
341+ skip("Unstable test; panel expansion refactor may be required");
342+
341 panel.fullscreenMode = data.fullscreen;
343 panel.fullscreenMode = data.fullscreen;
342 callManager.foregroundCall = data.call;
344 callManager.foregroundCall = data.call;
343
345
@@ -799,5 +801,39 @@ PanelTest {
799 tryCompare(appTitle, "visible", false, undefined, "App title should still be visible on mouse hover when panel decorations are visible");
801 tryCompare(appTitle, "visible", false, undefined, "App title should still be visible on mouse hover when panel decorations are visible");
800 tryCompare(appMenuBar, "visible", true, undefined, "App menu bar should be visible on mouse hover when panel decorations not visible");
802 tryCompare(appMenuBar, "visible", true, undefined, "App menu bar should be visible on mouse hover when panel decorations not visible");
801 }
803 }
804+
805+ function test_keyboardNavigation_data() {
806+ return [
807+ {tag: "tab to start", doTab: false},
808+ {tag: "no tab to start", doTab: true}
809+ ]
810+ }
811+
812+ function test_keyboardNavigation(data) {
813+ var indicatorsBar = findChild(panel.indicators, "indicatorsBar");
814+
815+ pullDownIndicatorsMenu();
816+
817+ indicatorsBar.setCurrentItemIndex(0);
818+
819+ if (data.doTab) {
820+ keyClick(Qt.Key_Tab);
821+ }
822+
823+ keyClick(Qt.Key_Right);
824+ tryCompare(indicatorsBar, "currentItemIndex", 1);
825+
826+ keyClick(Qt.Key_Right);
827+ tryCompare(indicatorsBar, "currentItemIndex", 2);
828+
829+ keyClick(Qt.Key_Left);
830+ tryCompare(indicatorsBar, "currentItemIndex", 1);
831+
832+ keyClick(Qt.Key_Left);
833+ tryCompare(indicatorsBar, "currentItemIndex", 0);
834+
835+ keyClick(Qt.Key_Escape);
836+ tryCompare(panel.indicators, "fullyClosed", true);
837+ }
802 }
838 }
803}
839}
tests/qmltests/Tutorial/tst_Tutorial.qml+3−3
@@ -17,7 +17,8 @@
17import QtQuick 2.4
17import QtQuick 2.4
18import QtTest 1.0
18import QtTest 1.0
19import AccountsService 0.1
19import AccountsService 0.1
20+import LightDMController 0.1
20−import LightDM.IntegratedLightDM 0.1 as LightDM
21+import LightDM.FullLightDM 0.1 as LightDM
21import Ubuntu.Components 1.3
22import Ubuntu.Components 1.3
22import Ubuntu.Components.ListItems 1.3
23import Ubuntu.Components.ListItems 1.3
23import Ubuntu.Telephony 0.1 as Telephony
24import Ubuntu.Telephony 0.1 as Telephony
@@ -85,8 +86,7 @@ Rectangle {
85
86
86 Component.onCompleted: {
87 Component.onCompleted: {
87 // must set the mock mode before loading the Shell
88 // must set the mock mode before loading the Shell
88− LightDM.Greeter.mockMode = "single-pin";
89+ LightDMController.userMode = "single-pin";
89− LightDM.Users.mockMode = "single-pin";
90 shellLoader.active = true;
90 shellLoader.active = true;
91 }
91 }
92
92
tests/qmltests/tst_OrientedShell.qml+90−8
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2015-2016 Canonical, Ltd.
2+ * Copyright (C) 2015-2017 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -22,7 +22,8 @@ import Ubuntu.Components 1.3
22import Ubuntu.Components.ListItems 1.3 as ListItem
22import Ubuntu.Components.ListItems 1.3 as ListItem
23import Unity.Application 0.1
23import Unity.Application 0.1
24import Unity.Test 0.1
24import Unity.Test 0.1
25+import LightDMController 0.1
25−import LightDM.IntegratedLightDM 0.1 as LightDM
26+import LightDM.FullLightDM 0.1 as LightDM
26import Powerd 0.1
27import Powerd 0.1
27import Unity.InputInfo 0.1
28import Unity.InputInfo 0.1
28import Utils 0.1
29import Utils 0.1
@@ -38,6 +39,12 @@ Rectangle {
38 width: units.gu(160) + controls.width
39 width: units.gu(160) + controls.width
39 height: units.gu(100)
40 height: units.gu(100)
40
41
42+ Binding {
43+ target: LightDMController
44+ property: "userMode"
45+ value: "single"
46+ }
47+
41 QtObject {
48 QtObject {
42 id: applicationArguments
49 id: applicationArguments
43 property string deviceName: "mako"
50 property string deviceName: "mako"
@@ -1365,12 +1372,7 @@ Rectangle {
1365 }
1372 }
1366
1373
1367 function findAppWindowForSurfaceId(surfaceId) {
1374 function findAppWindowForSurfaceId(surfaceId) {
1368− // for PhoneStage and TabletStage
1369− var delegate = findChild(shell, "spreadDelegate_" + surfaceId);
1375+ var delegate = findChild(shell, "appDelegate_" + surfaceId);
1370− if (!delegate) {
1371− // for DesktopStage
1372− delegate = findChild(shell, "appDelegate_" + surfaceId);
1373− }
1374 verify(delegate);
1376 verify(delegate);
1375 var appWindow = findChild(delegate, "appWindow");
1377 var appWindow = findChild(delegate, "appWindow");
1376 return appWindow;
1378 return appWindow;
@@ -1558,5 +1560,85 @@ Rectangle {
1558 verify(surface);
1560 verify(surface);
1559 return surface.activeFocus;
1561 return surface.activeFocus;
1560 }
1562 }
1563+
1564+ function test_tabCyclyingInShutdownDialog_data() {
1565+ return [
1566+ {tag: "TAB", key: Qt.Key_Tab},
1567+ {tag: "DOWN", key: Qt.Key_Down}
1568+ ];
1569+ }
1570+
1571+ function test_tabCyclyingInShutdownDialog(data) {
1572+ loadShell("mako");
1573+
1574+ testCase.showPowerDialog();
1575+
1576+ var dialogs = findChild(orientedShell, "dialogs");
1577+ var buttons = findChildsByType(dialogs, "Button");
1578+
1579+ tryCompare(buttons[0], "activeFocus", true);
1580+
1581+ keyClick(data.key);
1582+ tryCompare(buttons[1], "activeFocus", true);
1583+
1584+ keyClick(data.key);
1585+ tryCompare(buttons[2], "activeFocus", true);
1586+
1587+ keyClick(data.key);
1588+ tryCompare(buttons[0], "activeFocus", true);
1589+
1590+ keyClick(Qt.Key_Escape);
1591+
1592+ var dialogLoader = findChild(orientedShell, "dialogLoader");
1593+ tryCompare(dialogLoader, "item", null);
1594+ }
1595+
1596+ function test_escClosesShutdownDialog() {
1597+ loadShell("mako");
1598+
1599+ testCase.showPowerDialog();
1600+
1601+ var dialogLoader = findChild(orientedShell, "dialogLoader");
1602+ tryCompareFunction(function() { return dialogLoader.item !== null }, true);
1603+
1604+ keyClick(Qt.Key_Escape);
1605+
1606+ tryCompare(dialogLoader, "item", null);
1607+ }
1608+
1609+ function test_focusOnShutdownDialogClose() {
1610+ loadShell("manta");
1611+ usageModeSelector.selectWindowed();
1612+
1613+ var surfaceId = topLevelSurfaceList.nextId;
1614+ var app = ApplicationManager.startApplication("twitter-webapp");
1615+ waitUntilAppWindowIsFullyLoaded(surfaceId);
1616+
1617+ var primaryAppWindow = findAppWindowForSurfaceId(surfaceId);
1618+ var surface = app.surfaceList.get(0);
1619+ var surfaceItem = findSurfaceItem(primaryAppWindow, surface);
1620+
1621+ compare(window.activeFocusItem, surfaceItem);
1622+
1623+ testCase.showPowerDialog();
1624+
1625+ var dialogLoader = findChild(orientedShell, "dialogLoader");
1626+ tryCompareFunction(function() { return dialogLoader.item !== null }, true);
1627+
1628+ keyClick(Qt.Key_Escape);
1629+
1630+ tryCompare(dialogLoader, "item", null);
1631+ compare(window.activeFocusItem, surfaceItem);
1632+
1633+ // Do it twice, our previous solution failed the second time ^_^
1634+ testCase.showPowerDialog();
1635+
1636+ tryCompareFunction(function() { return dialogLoader.item !== null }, true);
1637+
1638+ keyClick(Qt.Key_Escape);
1639+
1640+ tryCompare(dialogLoader, "item", null);
1641+ compare(window.activeFocusItem, surfaceItem);
1642+ }
1561 }
1643 }
1562}
1644}
tests/qmltests/tst_Shell.qml+86−80
@@ -18,7 +18,8 @@ import QtQuick 2.4
18import QtTest 1.0
18import QtTest 1.0
19import AccountsService 0.1
19import AccountsService 0.1
20import GSettings 1.0
20import GSettings 1.0
21+import LightDMController 0.1
21−import LightDM.IntegratedLightDM 0.1 as LightDM
22+import LightDM.FullLightDM 0.1 as LightDM
22import SessionBroadcast 0.1
23import SessionBroadcast 0.1
23import Ubuntu.Components 1.3
24import Ubuntu.Components 1.3
24import Ubuntu.Components.ListItems 1.3 as ListItem
25import Ubuntu.Components.ListItems 1.3 as ListItem
@@ -48,8 +49,7 @@ Rectangle {
48
49
49 Component.onCompleted: {
50 Component.onCompleted: {
50 // must set the mock mode before loading the Shell
51 // must set the mock mode before loading the Shell
51− LightDM.Greeter.mockMode = "single";
52+ LightDMController.userMode = "single";
52− LightDM.Users.mockMode = "single";
53 shellLoader.active = true;
53 shellLoader.active = true;
54 }
54 }
55
55
@@ -248,8 +248,7 @@ Rectangle {
248 model: ["single", "single-passphrase", "single-pin", "full"]
248 model: ["single", "single-passphrase", "single-pin", "full"]
249 onSelectedIndexChanged: {
249 onSelectedIndexChanged: {
250 testCase.tearDown();
250 testCase.tearDown();
251− LightDM.Greeter.mockMode = model[selectedIndex];
252− LightDM.Users.mockMode = model[selectedIndex];
251+ LightDMController.userMode = model[selectedIndex];
253 shellLoader.active = true;
252 shellLoader.active = true;
254 }
253 }
255 }
254 }
@@ -568,7 +567,6 @@ Rectangle {
568 // is loaded by default
567 // is loaded by default
569 tearDown();
568 tearDown();
570 }
569 }
571−
572 }
570 }
573
571
574 function cleanup() {
572 function cleanup() {
@@ -656,7 +654,8 @@ Rectangle {
656 // Shell instance gets destroyed.
654 // Shell instance gets destroyed.
657 tryCompare(shellLoader, "itemDestroyed", true);
655 tryCompare(shellLoader, "itemDestroyed", true);
658
656
657+ LightDMController.reset();
659− setLightDMMockMode("single"); // back to the default value
658+ setLightDMMockMode("single"); // these tests default to "single"
660
659
661 AccountsService.demoEdges = false;
660 AccountsService.demoEdges = false;
662 AccountsService.demoEdgesCompleted = [];
661 AccountsService.demoEdgesCompleted = [];
@@ -747,22 +746,6 @@ Rectangle {
747 mockNotificationsModel.append(n)
746 mockNotificationsModel.append(n)
748 }
747 }
749
748
750− function test_ClickUbuntuIconInLauncherTakesToAppsAndResetSearchString() {
751− loadShell("phone");
752− swipeAwayGreeter();
753− dragLauncherIntoView();
754− dashCommunicatorSpy.clear();
755−
756− var launcher = findChild(shell, "launcher");
757− var dashIcon = findChild(launcher, "dashItem");
758− verify(dashIcon != undefined);
759− mouseClick(dashIcon);
760−
761− tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash");
762−
763− compare(dashCommunicatorSpy.count, 1);
764− }
765−
766 function test_suspend() {
749 function test_suspend() {
767 loadShell("phone");
750 loadShell("phone");
768 swipeAwayGreeter();
751 swipeAwayGreeter();
@@ -834,13 +817,15 @@ Rectangle {
834 keyClick(Qt.Key_Escape) // Reset state if we're not moving
817 keyClick(Qt.Key_Escape) // Reset state if we're not moving
835 while (userlist.currentIndex != i) {
818 while (userlist.currentIndex != i) {
836 var next = userlist.currentIndex + 1
819 var next = userlist.currentIndex + 1
820+ var key = Qt.Key_Down;
837 if (userlist.currentIndex > i) {
821 if (userlist.currentIndex > i) {
838 next = userlist.currentIndex - 1
822 next = userlist.currentIndex - 1
823+ key = Qt.Key_Up;
839 }
824 }
840− tap(findChild(greeter, "username"+next));
825+ keyPress(key);
841 tryCompare(userlist, "currentIndex", next)
826 tryCompare(userlist, "currentIndex", next)
842− tryCompare(userlist, "movingInternally", false)
843 }
827 }
828+ tryCompare(userlist, "movingInternally", false);
844 tryCompare(shell, "waitingOnGreeter", false); // wait for PAM to settle
829 tryCompare(shell, "waitingOnGreeter", false); // wait for PAM to settle
845 }
830 }
846
831
@@ -863,15 +848,13 @@ Rectangle {
863 var greeter = findChild(shell, "greeter")
848 var greeter = findChild(shell, "greeter")
864 tryCompare(greeter, "fullyShown", true);
849 tryCompare(greeter, "fullyShown", true);
865
850
866− var passwordInput = findChild(shell, "passwordInput");
867−
868− var promptButton = findChild(passwordInput, "promptButton");
851+ var promptButton = findChild(greeter, "promptButton");
869 tryCompare(promptButton, "visible", isButton);
852 tryCompare(promptButton, "visible", isButton);
870
853
871− var promptField = findChild(passwordInput, "promptField");
854+ var promptField = findChild(greeter, "promptField");
872 tryCompare(promptField, "visible", !isButton);
855 tryCompare(promptField, "visible", !isButton);
873
856
874− mouseClick(passwordInput)
857+ mouseClick(promptButton);
875 }
858 }
876
859
877 function confirmLoggedIn(loggedIn) {
860 function confirmLoggedIn(loggedIn) {
@@ -881,8 +864,7 @@ Rectangle {
881 }
864 }
882
865
883 function setLightDMMockMode(mode) {
866 function setLightDMMockMode(mode) {
884− LightDM.Greeter.mockMode = mode;
867+ LightDMController.userMode = mode;
885− LightDM.Users.mockMode = mode;
886 }
868 }
887
869
888 function test_showInputMethod() {
870 function test_showInputMethod() {
@@ -1380,55 +1362,25 @@ Rectangle {
1380 tryCompare(stage, "state", "spread");
1362 tryCompare(stage, "state", "spread");
1381 }
1363 }
1382
1364
1383− function test_tapUbuntuIconInLauncherOverAppSpread() {
1365+ function test_physicalHomeKeyPressDoesNothingWithActiveGreeter() {
1384− launcherShowDashHomeSpy.clear();
1385−
1386 loadShell("phone");
1366 loadShell("phone");
1387− swipeAwayGreeter();
1388−
1389− waitUntilFocusedApplicationIsShowingItsSurface();
1390
1367
1391− swipeFromRightEdgeToShowAppSpread();
1368+ var windowInputMonitor = findInvisibleChild(shell, "windowInputMonitor");
1392−
1393− var launcher = findChild(shell, "launcher");
1394−
1395− dragLauncherIntoView();
1396−
1397− // Emulate a tap with a finger, where the touch position drifts during the tap.
1398− // This is to test the touch ownership changes. The tap is happening on the button
1399− // area but then drifting into the left edge drag area. This test makes sure
1400− // the touch ownership stays with the button and doesn't move over to the
1401− // left edge drag area.
1402− {
1403− var buttonShowDashHome = findChild(launcher, "buttonShowDashHome");
1404− touchFlick(buttonShowDashHome,
1405− buttonShowDashHome.width * 0.2, /* startPos.x */
1406− buttonShowDashHome.height * 0.8, /* startPos.y */
1407− buttonShowDashHome.width * 0.8, /* endPos.x */
1408− buttonShowDashHome.height * 0.2 /* endPos.y */);
1409− }
1410−
1411− compare(launcherShowDashHomeSpy.count, 1);
1412−
1413− // check that the stage has left spread mode.
1414− {
1415− var stage = findChild(shell, "stage");
1369+ var coverPage = findChild(shell, "coverPage");
1416− tryCompare(stage, "state", "staged");
1417− }
1418
1370
1419− // check that the launcher got dismissed
1371+ windowInputMonitor.homeKeyActivated();
1420− var launcherPanel = findChild(shell, "launcherPanel");
1372+ verify(coverPage.shown);
1421− tryCompare(launcherPanel, "x", -launcherPanel.width);
1422 }
1373 }
1423
1374
1424− function test_physicalHomeKeyPressDoesNothingWithActiveGreeter() {
1375+ function test_physicalHomeKeyPressFocusesDash() {
1425 loadShell("phone");
1376 loadShell("phone");
1426
1377
1427− var windowInputMonitor = findInvisibleChild(shell, "windowInputMonitor");
1378+ var galleryApp = ApplicationManager.startApplication("gallery-app");
1428− var coverPage = findChild(shell, "coverPage");
1379+ tryCompare(ApplicationManager, "focusedApplicationId", "gallery-app");
1429
1380
1381+ var windowInputMonitor = findInvisibleChild(shell, "windowInputMonitor");
1430 windowInputMonitor.homeKeyActivated();
1382 windowInputMonitor.homeKeyActivated();
1431− verify(coverPage.shown);
1383+ tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash");
1432 }
1384 }
1433
1385
1434 function test_tabletLogin_data() {
1386 function test_tabletLogin_data() {
@@ -1475,11 +1427,39 @@ Rectangle {
1475 confirmLoggedIn(data.loggedIn)
1427 confirmLoggedIn(data.loggedIn)
1476
1428
1477 if (data.passwordFocus) {
1429 if (data.passwordFocus) {
1478− var passwordInput = findChild(greeter, "passwordInput")
1430+ var passwordInput = findChild(greeter, "promptField");
1479 tryCompare(passwordInput, "focus", true)
1431 tryCompare(passwordInput, "focus", true)
1480 }
1432 }
1481 }
1433 }
1482
1434
1435+ function test_manualLoginFlow() {
1436+ LightDMController.showManualLoginHint = true;
1437+ setLightDMMockMode("full");
1438+ loadShell("desktop");
1439+
1440+ var i = selectUser("*other");
1441+ var greeter = findChild(shell, "greeter");
1442+ var username = findChild(greeter, "username" + i);
1443+ var promptField = findChild(greeter, "promptField");
1444+ var promptHint = findChild(greeter, "promptHint");
1445+
1446+ compare(username.text, "Login");
1447+ compare(promptHint.text, "Username");
1448+
1449+ tryCompare(promptField, "activeFocus", true);
1450+ typeString("has-password");
1451+ keyClick(Qt.Key_Enter);
1452+
1453+ promptHint = findChild(greeter, "promptHint");
1454+ tryCompare(username, "text", "has-password");
1455+ tryCompare(promptHint, "text", "Passphrase");
1456+
1457+ typeString("password");
1458+ keyClick(Qt.Key_Enter);
1459+
1460+ confirmLoggedIn(true);
1461+ }
1462+
1483 function test_launcherInverted_data() {
1463 function test_launcherInverted_data() {
1484 return [
1464 return [
1485 {tag: "phone", formFactor: "phone", usageScenario: "phone", launcherInverted: true},
1465 {tag: "phone", formFactor: "phone", usageScenario: "phone", launcherInverted: true},
@@ -1847,7 +1827,7 @@ Rectangle {
1847 loadDesktopShellWithApps()
1827 loadDesktopShellWithApps()
1848 var launcher = findChild(shell, "launcher");
1828 var launcher = findChild(shell, "launcher");
1849 var stage = findChild(shell, "stage");
1829 var stage = findChild(shell, "stage");
1850− var bfb = findChild(launcher, "buttonShowDashHome");
1830+ var app1 = findChild(launcher, "launcherDelegate0");
1851
1831
1852 GSettingsController.setAutohideLauncher(!data.launcherLocked);
1832 GSettingsController.setAutohideLauncher(!data.launcherLocked);
1853 waitForRendering(shell);
1833 waitForRendering(shell);
@@ -1860,17 +1840,17 @@ Rectangle {
1860 if (!data.launcherLocked) {
1840 if (!data.launcherLocked) {
1861 revealLauncherByEdgePushWithMouse();
1841 revealLauncherByEdgePushWithMouse();
1862 tryCompare(launcher, "x", 0);
1842 tryCompare(launcher, "x", 0);
1863− mouseMove(bfb, bfb.width / 2, bfb.height / 2)
1843+ mouseMove(app1, app1.width / 2, app1.height / 2)
1864 waitForRendering(shell)
1844 waitForRendering(shell)
1865 }
1845 }
1866
1846
1867− mouseClick(bfb, bfb.width / 2, bfb.height / 2)
1847+ mouseClick(app1, app1.width / 2, app1.height / 2)
1868 if (!data.launcherLocked) {
1848 if (!data.launcherLocked) {
1869 tryCompare(launcher, "state", "")
1849 tryCompare(launcher, "state", "")
1870 }
1850 }
1871 tryCompare(stage, "state", "windowed")
1851 tryCompare(stage, "state", "windowed")
1872
1852
1873− tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash")
1853+ tryCompare(ApplicationManager, "focusedApplicationId", "dialer-app")
1874
1854
1875 keyRelease(Qt.Key_Alt);
1855 keyRelease(Qt.Key_Alt);
1876 }
1856 }
@@ -2194,10 +2174,8 @@ Rectangle {
2194 tryCompare(launcher, "state", "visible");
2174 tryCompare(launcher, "state", "visible");
2195 tryCompare(launcherPanel, "highlightIndex", -1);
2175 tryCompare(launcherPanel, "highlightIndex", -1);
2196 keyRelease(Qt.Key_Super_L, Qt.MetaModifier);
2176 keyRelease(Qt.Key_Super_L, Qt.MetaModifier);
2197− tryCompare(launcher, "state", data.launcherLocked ? "visible" : "");
2177+ tryCompare(launcher, "state", "drawer");
2198 tryCompare(launcherPanel, "highlightIndex", -2);
2178 tryCompare(launcherPanel, "highlightIndex", -2);
2199− tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash");
2200− tryCompare(stage, "focus", true)
2201 }
2179 }
2202
2180
2203 function test_longpressSuperOpensLauncherAndShortcutsOverlay() {
2181 function test_longpressSuperOpensLauncherAndShortcutsOverlay() {
@@ -2773,5 +2751,33 @@ Rectangle {
2773
2751
2774 tryCompare(panelTitle, "visible", true, undefined, "Panel title should be visible when greeter not shown");
2752 tryCompare(panelTitle, "visible", true, undefined, "Panel title should be visible when greeter not shown");
2775 }
2753 }
2754+
2755+ function test_fourFingerTapOpensDrawer_data() {
2756+ return [
2757+ { tag: "1 finger", touchIds: [0], shown: false },
2758+ { tag: "2 finger", touchIds: [0, 1], shown: false },
2759+ { tag: "3 finger", touchIds: [0, 1, 2], shown: false },
2760+ { tag: "4 finger", touchIds: [0, 1, 2, 3], shown: true }
2761+ ];
2762+ }
2763+
2764+ function test_fourFingerTapOpensDrawer(data) {
2765+ loadShell("desktop");
2766+ shell.usageScenario = "desktop";
2767+ waitForRendering(shell);
2768+ swipeAwayGreeter();
2769+
2770+ var stage = findChild(shell, "stage");
2771+ var drawer = findChild(shell, "drawer")
2772+ verify(stage && drawer);
2773+
2774+ multiTouchTap(data.touchIds, stage);
2775+
2776+ if (data.shown) { // if shown, try to also hide it by clicking outside
2777+ tryCompareFunction(function() { return drawer.visible; }, true);
2778+ mouseClick(stage, stage.width-10, stage.height/2, undefined, undefined, 100);
2779+ }
2780+ tryCompareFunction(function() { return drawer.visible; }, false);
2781+ }
2776 }
2782 }
2777}
2783}
tests/qmltests/tst_ShellWithPin.qml+22−13
@@ -19,7 +19,8 @@ import Ubuntu.Components 1.3
19import QtTest 1.0
19import QtTest 1.0
20import AccountsService 0.1
20import AccountsService 0.1
21import GSettings 1.0
21import GSettings 1.0
22+import LightDMController 0.1
22−import LightDM.IntegratedLightDM 0.1 as LightDM
23+import LightDM.FullLightDM 0.1 as LightDM
23import Ubuntu.SystemImage 0.1
24import Ubuntu.SystemImage 0.1
24import Ubuntu.Telephony 0.1 as Telephony
25import Ubuntu.Telephony 0.1 as Telephony
25import Unity.Application 0.1
26import Unity.Application 0.1
@@ -35,8 +36,7 @@ Item {
35
36
36 Component.onCompleted: {
37 Component.onCompleted: {
37 // must set the mock mode before loading the Shell
38 // must set the mock mode before loading the Shell
38− LightDM.Greeter.mockMode = "single-pin";
39+ LightDMController.userMode = "single-pin";
39− LightDM.Users.mockMode = "single-pin";
40 shellLoader.active = true;
40 shellLoader.active = true;
41 }
41 }
42
42
@@ -117,12 +117,6 @@ Item {
117 signalName: "resettingDevice"
117 signalName: "resettingDevice"
118 }
118 }
119
119
120− SignalSpy {
121− id: promptSpy
122− target: LightDM.Greeter
123− signalName: "showPrompt"
124− }
125−
126 Telephony.CallEntry {
120 Telephony.CallEntry {
127 id: phoneCall
121 id: phoneCall
128 phoneNumber: "+447812221111"
122 phoneNumber: "+447812221111"
@@ -539,24 +533,39 @@ Item {
539 shellLoader.itemDestroyed = false;
533 shellLoader.itemDestroyed = false;
540 shellLoader.active = false;
534 shellLoader.active = false;
541 tryCompare(shellLoader, "itemDestroyed", true);
535 tryCompare(shellLoader, "itemDestroyed", true);
542− LightDM.Greeter.authenticate(""); // reset greeter
543
536
544 // Create new shell
537 // Create new shell
545− promptSpy.clear();
546 shellLoader.active = true;
538 shellLoader.active = true;
547 tryCompareFunction(function() {return shell !== null}, true);
539 tryCompareFunction(function() {return shell !== null}, true);
548
540
549 // Confirm that we start disabled
541 // Confirm that we start disabled
550− compare(promptSpy.count, 0);
542+ compare(LightDM.Prompts.count, 0);
551 verify(shell.waitingOnGreeter);
543 verify(shell.waitingOnGreeter);
552 var coverPageDragHandle = findChild(shell, "coverPageDragHandle");
544 var coverPageDragHandle = findChild(shell, "coverPageDragHandle");
553 verify(!coverPageDragHandle.enabled);
545 verify(!coverPageDragHandle.enabled);
554
546
555 // And that we only become enabled once the lockscreen is up
547 // And that we only become enabled once the lockscreen is up
556 tryCompare(shell, "waitingOnGreeter", false);
548 tryCompare(shell, "waitingOnGreeter", false);
557− verify(promptSpy.count > 0);
549+ verify(LightDM.Prompts.count > 0);
558 var lockscreen = findChild(shell, "lockscreen");
550 var lockscreen = findChild(shell, "lockscreen");
559 verify(lockscreen.shown);
551 verify(lockscreen.shown);
560 }
552 }
553+
554+ function test_bfbOnLockedDevice() {
555+ var launcher = findChild(shell, "launcher");
556+ touchFlick(shell, units.gu(.5), shell.height / 2, units.gu(10), shell.height / 2);
557+
558+ tryCompare(launcher, "x", 0);
559+ tryCompare(launcher, "state", "visible");
560+
561+ waitForRendering(shell)
562+
563+ var bfb = findChild(launcher, "buttonShowDashHome");
564+ mouseClick(bfb, bfb.width / 2, bfb.height / 2);
565+
566+ enterPin("1234")
567+
568+ tryCompare(launcher, "state", "drawer");
569+ }
561 }
570 }
562}
571}