on xenial help sign in
58 files changed+3429−2687
since splitsnapshots
unifiedsplit
README.md+3−3 clickable.json−158 clickable.yaml+153 plugins/Lights/CMakeLists.txt+5−1 plugins/Lights/HfdLights.cpp+62 plugins/Lights/HfdLights.h+42 plugins/Lights/LegacyLights.cpp+91 plugins/Lights/LegacyLights.h+45 plugins/Lights/Lights.cpp−65 plugins/Lights/Lights.h+4−8 plugins/Lights/plugin.cpp+20−1 plugins/WindowManager/TopLevelWindowModel.cpp+4−3 po/am.po+4−6 po/ar.po+20−25 po/cs.po+12−12 po/el.po+4−6 po/es.po+22−22 po/he.po+9−11 po/hu.po+9−11 po/it.po+6−6 po/km.po+11−14 po/nb.po+2−4 po/nl.po+3−3 po/pt.po+4−4 po/sc.po+6−6 po/sv.po+25−52 po/th.po+20−20 po/ur.po+31−28 qml/Greeter/ButtonPrompt.qml+51 qml/Greeter/CoverPage.qml+27−8 qml/Greeter/Greeter.qml+37−2 qml/Greeter/GreeterPrompt.qml+64−187 qml/Greeter/GreeterView.qml+534 qml/Greeter/LoginAreaContainer.qml+3−3 qml/Greeter/LoginList.qml+71−98 qml/Greeter/NarrowView.qml−274 qml/Greeter/PinPrompt.qml+184 qml/Greeter/PromptList.qml+21−18 qml/Greeter/SessionsList.qml+3−2 qml/Greeter/TextPrompt.qml+198 qml/Greeter/WideView.qml−210 qml/Launcher/BackgroundBlur.qml+81 qml/Launcher/Drawer.qml+8−2 qml/Launcher/Launcher.qml+23−1 qml/OrientedShell.qml+1 qml/Panel/Indicators/IndicatorItem.qml+6 qml/Rotation/RotationStates.qml−3 qml/Shell.qml+10−3 qml/Stage/ApplicationWindow.qml+12−3 src/ApplicationArguments.h+5 src/ShellApplication.cpp+6 tests/qmltests/CMakeLists.txt+1−2 tests/qmltests/Greeter/TestView.qml+7 tests/qmltests/Greeter/tst_GreeterView.qml+1366 tests/qmltests/Greeter/tst_NarrowView.qml−565 tests/qmltests/Greeter/tst_WideView.qml−781 tests/qmltests/tst_OrientedShell.qml+45−41 tests/qmltests/tst_Shell.qml+48−15
README.md+3−3
@@ -22,10 +22,10 @@ Now, clone this repository to your computer: `git clone https://github.com/ubpor
2222
2323Move into this directory: `cd unity8`
2424
25−Now you can use the full suite of tools provided by this repository's [clickable.json](clickable.json). For example:
25+Now you can use the full suite of tools provided by this repository's [clickable.yaml](clickable.yaml). For example:
2626
2727* `clickable ide qtcreator` will open QtCreator with this repository open as a project. You can edit and build the project this way, but running the tests will be a bit difficult.
28−* `clickable build-libs --debug` will build Lomiri in the same environment as it would receive in Ubuntu Touch.
29−* `clickable test-libs` will run the entire Lomiri test suite. This test suite includes graphical tests (which are run on a virtual, invisible desktop) and non-graphical unit tests. These tests make sure Lomiri functions as prescribed and prevents new bugs from being added. You should run them before you create a PR on this repository. This command takes about 7 minutes on an Intel i7-8550U, so plan your time accordingly and use the next two options to reduce the number of times you need to run the whole test suite...
28+* `clickable build --libs --verbose` will build Lomiri in the same environment as it would receive in Ubuntu Touch.
29+* `clickable test --libs` will run the entire Lomiri test suite. This test suite includes graphical tests (which are run on a virtual, invisible desktop) and non-graphical unit tests. These tests make sure Lomiri functions as prescribed and prevents new bugs from being added. You should run them before you create a PR on this repository. This command takes about 7 minutes on an Intel i7-8550U, so plan your time accordingly and use the next two options to reduce the number of times you need to run the whole test suite...
3030* `clickable ide 'cd build/x86_64-linux-gnu/unity8/ && make tryShell'` and similar commands that replace the `Shell` with another test target defined in [tests/qmltests/CMakeLists.txt](tests/qmltests/CMakeLists.txt) allow trying out some Lomiri components in a mock environment. You can use this to try out your changes to Lomiri as you develop.
3131* `clickable ide 'cd build/x86_64-linux-gnu/unity8/ && make xvfbtestShell'` and similar commands that replace the `Shell` with another test target defined in [tests/qmltests/CMakeLists.txt](tests/qmltests/CMakeLists.txt) allow running a single TestCase. You can remove the `xvfb` part of the make target to run the test case graphically, which might help you see what is causing the test to fail.
clickable.jsondeleted−158
@@ -1,158 +0,0 @@
1−{
2− "builder": "custom",
3− "build": "echo Run \\'clickable build-libs --debug\\' to build && false",
4− "restrict_arch": "amd64",
5− "libraries": {
6− "unity8": {
7− "builder": "cmake",
8− "src_dir": "${ROOT}",
9− "test": "make xvfballtests -j${NUM_PROCS}",
10− "dependencies_host": [
11− "android-headers",
12− "cmake",
13− "cmake-extras",
14− "dbus-test-runner",
15− "debhelper",
16− "dh-apparmor",
17− "doxyqml",
18− "g++:native",
19− "gdb",
20− "graphviz",
21− "gsettings-ubuntu-schemas",
22− "libandroid-properties-dev",
23− "libconnectivity-qt1-dev",
24− "libevdev-dev",
25− "libgeonames-dev",
26− "libgl1-mesa-dev",
27− "libgl1-mesa-dri",
28− "libgles2-mesa-dev",
29− "libglib2.0-dev",
30− "libgnome-desktop-3-12",
31− "libgnome-desktop-3-dev",
32− "libgsettings-qt-dev",
33− "libhardware-dev",
34− "liblightdm-qt5-3-dev",
35− "libpam0g-dev",
36− "libpulse-dev",
37− "libqmenumodel-dev",
38− "libqt5sql5-sqlite",
39− "libqt5svg5-dev",
40− "libqt5xmlpatterns5-dev",
41− "libqtdbusmock1-dev",
42− "libqtdbustest1-dev",
43− "libsystemsettings-dev",
44− "libubuntu-app-launch2-dev",
45− "libubuntu-download-manager-common-dev",
46− "libubuntugestures5-dev",
47− "libubuntugestures5-private-dev",
48− "libudev-dev",
49− "libudm-common-dev",
50− "libunity-api-dev",
51− "libusermetricsoutput1-dev",
52− "libx11-dev",
53− "libxcb1-dev",
54− "libxi-dev",
55− "pkg-config",
56− "python3-all:any",
57− "python3-setuptools:native",
58− "qml-module-qt-labs-folderlistmodel",
59− "qml-module-qt-labs-settings",
60− "qml-module-qtmultimedia",
61− "qml-module-qtqml-statemachine",
62− "qml-module-qtquick-layouts",
63− "qml-module-qtquick-xmllistmodel",
64− "qml-module-qtquick2",
65− "qml-module-qtsysteminfo",
66− "qml-module-qttest",
67− "qml-module-ubuntu-components",
68− "qml-module-ubuntu-layouts",
69− "qml-module-ubuntu-settings-components",
70− "qml-module-ubuntu-test",
71− "qtbase5-dev",
72− "qtbase5-dev-tools",
73− "qtbase5-private-dev",
74− "qtdbustest-runner",
75− "qtdeclarative5-dev",
76− "qtdeclarative5-dev-tools",
77− "qtdeclarative5-private-dev",
78− "qtdeclarative5-ubuntu-content1",
79− "ttf-ubuntu-font-family",
80− "ubports-wallpapers",
81− "xvfb"
82− ]
83− }
84− },
85− "dependencies_target": [
86− "android-headers",
87− "cmake",
88− "cmake-extras",
89− "dbus-test-runner",
90− "debhelper",
91− "dh-apparmor",
92− "doxyqml",
93− "g++:native",
94− "gdb",
95− "graphviz",
96− "gsettings-ubuntu-schemas",
97− "libandroid-properties-dev",
98− "libconnectivity-qt1-dev",
99− "libevdev-dev",
100− "libgeonames-dev",
101− "libgl1-mesa-dev",
102− "libgl1-mesa-dri",
103− "libgles2-mesa-dev",
104− "libglib2.0-dev",
105− "libgnome-desktop-3-12",
106− "libgnome-desktop-3-dev",
107− "libgsettings-qt-dev",
108− "libhardware-dev",
109− "liblightdm-qt5-3-dev",
110− "libpam0g-dev",
111− "libpulse-dev",
112− "libqmenumodel-dev",
113− "libqt5sql5-sqlite",
114− "libqt5svg5-dev",
115− "libqt5xmlpatterns5-dev",
116− "libqtdbusmock1-dev",
117− "libqtdbustest1-dev",
118− "libsystemsettings-dev",
119− "libubuntu-app-launch2-dev",
120− "libubuntu-download-manager-common-dev",
121− "libubuntugestures5-dev",
122− "libubuntugestures5-private-dev",
123− "libudev-dev",
124− "libudm-common-dev",
125− "libunity-api-dev",
126− "libusermetricsoutput1-dev",
127− "libx11-dev",
128− "libxcb1-dev",
129− "libxi-dev",
130− "pkg-config",
131− "python3-all:any",
132− "python3-setuptools:native",
133− "qml-module-qt-labs-folderlistmodel",
134− "qml-module-qt-labs-settings",
135− "qml-module-qtmultimedia",
136− "qml-module-qtqml-statemachine",
137− "qml-module-qtquick-layouts",
138− "qml-module-qtquick-xmllistmodel",
139− "qml-module-qtquick2",
140− "qml-module-qtsysteminfo",
141− "qml-module-qttest",
142− "qml-module-ubuntu-components",
143− "qml-module-ubuntu-layouts",
144− "qml-module-ubuntu-settings-components",
145− "qml-module-ubuntu-test",
146− "qtbase5-dev",
147− "qtbase5-dev-tools",
148− "qtbase5-private-dev",
149− "qtdbustest-runner",
150− "qtdeclarative5-dev",
151− "qtdeclarative5-dev-tools",
152− "qtdeclarative5-private-dev",
153− "qtdeclarative5-ubuntu-content1",
154− "ttf-ubuntu-font-family",
155− "ubports-wallpapers",
156− "xvfb"
157− ]
158− }
clickable.yamlnew file+153
@@ -0,0 +1,153 @@
1+clickable_minimum_required: '7'
2+builder: custom
3+build: echo Run \'clickable build --libs --verbose\' to build && false
4+restrict_arch: amd64
5+libraries:
6+ unity8:
7+ builder: cmake
8+ src_dir: "${ROOT}"
9+ test: make xvfballtests -j${NUM_PROCS}
10+ dependencies_host:
11+ - android-headers
12+ - cmake
13+ - cmake-extras
14+ - dbus-test-runner
15+ - debhelper
16+ - dh-apparmor
17+ - doxyqml
18+ - g++:native
19+ - gdb
20+ - graphviz
21+ - gsettings-ubuntu-schemas
22+ - libandroid-properties-dev
23+ - libconnectivity-qt1-dev
24+ - libevdev-dev
25+ - libgeonames-dev
26+ - libgl1-mesa-dev
27+ - libgl1-mesa-dri
28+ - libgles2-mesa-dev
29+ - libglib2.0-dev
30+ - libgnome-desktop-3-12
31+ - libgnome-desktop-3-dev
32+ - libgsettings-qt-dev
33+ - libhardware-dev
34+ - liblightdm-qt5-3-dev
35+ - libpam0g-dev
36+ - libpulse-dev
37+ - libqmenumodel-dev
38+ - libqt5sql5-sqlite
39+ - libqt5svg5-dev
40+ - libqt5xmlpatterns5-dev
41+ - libqtdbusmock1-dev
42+ - libqtdbustest1-dev
43+ - libsystemsettings-dev
44+ - libubuntu-app-launch2-dev
45+ - libubuntu-download-manager-common-dev
46+ - libubuntugestures5-dev
47+ - libubuntugestures5-private-dev
48+ - libudev-dev
49+ - libudm-common-dev
50+ - libunity-api-dev
51+ - libusermetricsoutput1-dev
52+ - libx11-dev
53+ - libxcb1-dev
54+ - libxi-dev
55+ - pkg-config
56+ - python3-all:any
57+ - python3-setuptools:native
58+ - qml-module-qt-labs-folderlistmodel
59+ - qml-module-qt-labs-settings
60+ - qml-module-qtmultimedia
61+ - qml-module-qtqml-statemachine
62+ - qml-module-qtquick-layouts
63+ - qml-module-qtquick-xmllistmodel
64+ - qml-module-qtquick2
65+ - qml-module-qtsysteminfo
66+ - qml-module-qttest
67+ - qml-module-ubuntu-components
68+ - qml-module-ubuntu-layouts
69+ - qml-module-ubuntu-settings-components
70+ - qml-module-ubuntu-test
71+ - qtbase5-dev
72+ - qtbase5-dev-tools
73+ - qtbase5-private-dev
74+ - qtdbustest-runner
75+ - qtdeclarative5-dev
76+ - qtdeclarative5-dev-tools
77+ - qtdeclarative5-private-dev
78+ - qtdeclarative5-ubuntu-content1
79+ - ttf-ubuntu-font-family
80+ - ubports-wallpapers
81+ - xvfb
82+dependencies_target:
83+ - android-headers
84+ - cmake
85+ - cmake-extras
86+ - dbus-test-runner
87+ - debhelper
88+ - dh-apparmor
89+ - doxyqml
90+ - g++:native
91+ - gdb
92+ - graphviz
93+ - gsettings-ubuntu-schemas
94+ - libandroid-properties-dev
95+ - libconnectivity-qt1-dev
96+ - libevdev-dev
97+ - libgeonames-dev
98+ - libgl1-mesa-dev
99+ - libgl1-mesa-dri
100+ - libgles2-mesa-dev
101+ - libglib2.0-dev
102+ - libgnome-desktop-3-12
103+ - libgnome-desktop-3-dev
104+ - libgsettings-qt-dev
105+ - libhardware-dev
106+ - liblightdm-qt5-3-dev
107+ - libpam0g-dev
108+ - libpulse-dev
109+ - libqmenumodel-dev
110+ - libqt5sql5-sqlite
111+ - libqt5svg5-dev
112+ - libqt5xmlpatterns5-dev
113+ - libqtdbusmock1-dev
114+ - libqtdbustest1-dev
115+ - libsystemsettings-dev
116+ - libubuntu-app-launch2-dev
117+ - libubuntu-download-manager-common-dev
118+ - libubuntugestures5-dev
119+ - libubuntugestures5-private-dev
120+ - libudev-dev
121+ - libudm-common-dev
122+ - libunity-api-dev
123+ - libusermetricsoutput1-dev
124+ - libx11-dev
125+ - libxcb1-dev
126+ - libxi-dev
127+ - pkg-config
128+ - python3-all:any
129+ - python3-setuptools:native
130+ - qml-module-qt-labs-folderlistmodel
131+ - qml-module-qt-labs-settings
132+ - qml-module-qtmultimedia
133+ - qml-module-qtqml-statemachine
134+ - qml-module-qtquick-layouts
135+ - qml-module-qtquick-xmllistmodel
136+ - qml-module-qtquick2
137+ - qml-module-qtsysteminfo
138+ - qml-module-qttest
139+ - qml-module-ubuntu-components
140+ - qml-module-ubuntu-layouts
141+ - qml-module-ubuntu-settings-components
142+ - qml-module-ubuntu-test
143+ - qtbase5-dev
144+ - qtbase5-dev-tools
145+ - qtbase5-private-dev
146+ - qtdbustest-runner
147+ - qtdeclarative5-dev
148+ - qtdeclarative5-dev-tools
149+ - qtdeclarative5-private-dev
150+ - qtdeclarative5-ubuntu-content1
151+ - ttf-ubuntu-font-family
152+ - ubports-wallpapers
153+ - xvfb
plugins/Lights/CMakeLists.txt+5−1
@@ -1,5 +1,6 @@
11pkg_check_modules(ANDROID_HEADERS REQUIRED android-headers)
22pkg_check_modules(ANDROID_HARDWARE REQUIRED libhardware)
3+pkg_check_modules(ANDROID_PROPERTIES REQUIRED libandroid-properties)
34
45include_directories(
56 SYSTEM
@@ -9,11 +10,14 @@ include_directories(
910add_library(Lights-qml MODULE
1011 plugin.cpp
1112 Lights.cpp
13+ LegacyLights.cpp
14+ HfdLights.cpp
1215 )
1316
1417target_link_libraries(Lights-qml
15− Qt5::Qml Qt5::Gui
18+ Qt5::Qml Qt5::Gui Qt5::DBus
1619 ${ANDROID_HARDWARE_LIBRARIES}
20+ ${ANDROID_PROPERTIES_LIBRARIES}
1721 )
1822
1923add_unity8_plugin(Lights 0.1 Lights TARGETS Lights-qml)
plugins/Lights/HfdLights.cppnew file+62
@@ -0,0 +1,62 @@
1+/*
2+ * Copyright (C) 2021 UBports Foundation.
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: Florian Leeber <florian@ubports.com>
17+ */
18+
19+#include "HfdLights.h"
20+#include <QtCore/QDebug>
21+#include <QDBusArgument>
22+#include <QDBusConnection>
23+#include <iostream>
24+
25+#define HFD_SERVICE_NAME "com.lomiri.hfd"
26+#define HFD_SERVICE_PATH "/com/lomiri/hfd"
27+#define HFD_SERVICE_INTERFACE "com.lomiri.hfd.Leds"
28+
29+HfdLights::HfdLights(QObject* parent)
30+ : Lights(parent)
31+{
32+ m_hfdInterface = new QDBusInterface(
33+ QStringLiteral(HFD_SERVICE_NAME),
34+ QStringLiteral(HFD_SERVICE_PATH),
35+ QStringLiteral(HFD_SERVICE_INTERFACE),
36+ QDBusConnection::systemBus(),
37+ this
38+ );
39+}
40+
41+bool HfdLights::init()
42+{
43+ // hfd does not need any initalization to be used
44+ return true;
45+}
46+
47+void HfdLights::turnOn()
48+{
49+ m_hfdInterface->call(QStringLiteral("setState"), 0);
50+ m_hfdInterface->call(QStringLiteral("setColor"), (quint32)m_color.rgba());
51+ m_hfdInterface->call(QStringLiteral("setOnMs"), m_onMs);
52+ m_hfdInterface->call(QStringLiteral("setOffMs"), m_offMs);
53+ m_hfdInterface->call(QStringLiteral("setState"), 1);
54+}
55+
56+void HfdLights::turnOff()
57+{
58+ m_hfdInterface->call(QStringLiteral("setState"), 0);
59+ m_hfdInterface->call(QStringLiteral("setColor"), (quint32)0);
60+ m_hfdInterface->call(QStringLiteral("setOnMs"), 0);
61+ m_hfdInterface->call(QStringLiteral("setOffMs"), 0);
62+}
plugins/Lights/HfdLights.hnew file+42
@@ -0,0 +1,42 @@
1+/*
2+ * Copyright (C) 2021 UBports Foundation.
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: Florian Leeber <florian@ubports.com>
17+ */
18+
19+#ifndef UNITY_HFDLIGHTS_H
20+#define UNITY_HFDLIGHTS_H
21+
22+#include <QtCore/QObject>
23+#include <QDBusInterface>
24+#include "Lights.h"
25+
26+class HfdLights: public Lights
27+{
28+ Q_OBJECT
29+
30+public:
31+ explicit HfdLights(QObject *parent = 0);
32+
33+protected:
34+ bool init() override;
35+ void turnOff() override;
36+ void turnOn() override;
37+
38+private:
39+ QDBusInterface* m_hfdInterface;
40+};
41+
42+#endif
plugins/Lights/LegacyLights.cppnew file+91
@@ -0,0 +1,91 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ * Copyright (C) 2021 UBports Foundation.
4+ *
5+ * This program is free software; you can redistribute it and/or modify
6+ * it under the terms of the GNU General Public License as published by
7+ * the Free Software Foundation; version 3.
8+ *
9+ * This program is distributed in the hope that it will be useful,
10+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+ * GNU General Public License for more details.
13+ *
14+ * You should have received a copy of the GNU General Public License
15+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
16+ *
17+ * Author: Renato Araujo Oliveira Filho <renato.filho@canonical.com>
18+ */
19+
20+#include "LegacyLights.h"
21+#include <QtCore/QDebug>
22+
23+extern "C" {
24+#include "android-hardware.h"
25+#include <string.h>
26+}
27+
28+LegacyLights::~LegacyLights()
29+{
30+ if (m_lightDevice) {
31+ hw_device_t* device = (hw_device_t*) m_lightDevice;
32+ device->close(device);
33+ }
34+}
35+
36+bool LegacyLights::init()
37+{
38+ if (m_lightDevice) {
39+ return true;
40+ }
41+
42+ int err;
43+ hw_module_t* module;
44+
45+ err = hw_get_module(LIGHTS_HARDWARE_MODULE_ID, (hw_module_t const**)&module);
46+ if (err == 0) {
47+ hw_device_t* device;
48+ err = module->methods->open(module, LIGHT_ID_NOTIFICATIONS, &device);
49+ if (err == 0) {
50+ m_lightDevice = (light_device_t*)device;
51+ turnOff();
52+ return true;
53+ } else {
54+ qWarning() << "Failed to access notification lights";
55+ }
56+ } else {
57+ qWarning() << "Failed to initialize lights hardware.";
58+ }
59+ return false;
60+}
61+
62+void LegacyLights::turnOn()
63+{
64+ // pulse
65+ light_state_t state;
66+ memset(&state, 0, sizeof(light_state_t));
67+ state.color = m_color.rgba();
68+ state.flashMode = LIGHT_FLASH_TIMED;
69+ state.flashOnMS = m_onMs;
70+ state.flashOffMS = m_offMs;
71+ state.brightnessMode = BRIGHTNESS_MODE_USER;
72+
73+ if (m_lightDevice->set_light(m_lightDevice, &state) != 0) {
74+ qWarning() << "Failed to turn the light off";
75+ }
76+}
77+
78+void LegacyLights::turnOff()
79+{
80+ light_state_t state;
81+ memset(&state, 0, sizeof(light_state_t));
82+ state.color = 0x00000000;
83+ state.flashMode = LIGHT_FLASH_NONE;
84+ state.flashOnMS = 0;
85+ state.flashOffMS = 0;
86+ state.brightnessMode = 0;
87+
88+ if (m_lightDevice->set_light(m_lightDevice, &state) != 0) {
89+ qWarning() << "Failed to turn the light off";
90+ }
91+}
plugins/Lights/LegacyLights.hnew file+45
@@ -0,0 +1,45 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ * Copyright (C) 2021 UBports Foundation.
4+ *
5+ * This program is free software; you can redistribute it and/or modify
6+ * it under the terms of the GNU General Public License as published by
7+ * the Free Software Foundation; version 3.
8+ *
9+ * This program is distributed in the hope that it will be useful,
10+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+ * GNU General Public License for more details.
13+ *
14+ * You should have received a copy of the GNU General Public License
15+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
16+ *
17+ * Author: Renato Araujo Oliveira Filho <renato.filho@canonical.com>
18+ */
19+
20+#ifndef UNITY_LEGACYLIGHTS_H
21+#define UNITY_LEGACYLIGHTS_H
22+
23+#include <QtCore/QObject>
24+#include "Lights.h"
25+
26+struct light_device_t;
27+
28+class LegacyLights: public Lights
29+{
30+ Q_OBJECT
31+
32+public:
33+ ~LegacyLights();
34+
35+protected:
36+ bool init() override;
37+ void turnOff() override;
38+ void turnOn() override;
39+
40+private:
41+ light_device_t* m_lightDevice;
42+
43+};
44+
45+#endif
plugins/Lights/Lights.cpp−65
@@ -26,20 +26,12 @@ extern "C" {
2626
2727Lights::Lights(QObject* parent)
2828 : QObject(parent),
29− m_lightDevice(0),
3029 m_color("blue"),
3130 m_state(Lights::Off),
3231 m_onMs(1000),
3332 m_offMs(3000)
3433{
35−}
3634
37−Lights::~Lights()
38−{
39− if (m_lightDevice) {
40− hw_device_t* device = (hw_device_t*) m_lightDevice;
41− device->close(device);
42− }
4335}
4436
4537void Lights::setState(Lights::State newState)
@@ -107,60 +99,3 @@ void Lights::setOffMillisec(int offMs)
10799 // FIXME: update the property if the light is already on
108100 }
109101}
110−
111−bool Lights::init()
112−{
113− if (m_lightDevice) {
114− return true;
115− }
116−
117− int err;
118− hw_module_t* module;
119−
120− err = hw_get_module(LIGHTS_HARDWARE_MODULE_ID, (hw_module_t const**)&module);
121− if (err == 0) {
122− hw_device_t* device;
123− err = module->methods->open(module, LIGHT_ID_NOTIFICATIONS, &device);
124− if (err == 0) {
125− m_lightDevice = (light_device_t*)device;
126− turnOff();
127− return true;
128− } else {
129− qWarning() << "Failed to access notification lights";
130− }
131− } else {
132− qWarning() << "Failed to initialize lights hardware.";
133− }
134− return false;
135−}
136−
137−void Lights::turnOn()
138−{
139− // pulse
140− light_state_t state;
141− memset(&state, 0, sizeof(light_state_t));
142− state.color = m_color.rgba();
143− state.flashMode = LIGHT_FLASH_TIMED;
144− state.flashOnMS = m_onMs;
145− state.flashOffMS = m_offMs;
146− state.brightnessMode = BRIGHTNESS_MODE_USER;
147−
148− if (m_lightDevice->set_light(m_lightDevice, &state) != 0) {
149− qWarning() << "Failed to turn the light off";
150− }
151−}
152−
153−void Lights::turnOff()
154−{
155− light_state_t state;
156− memset(&state, 0, sizeof(light_state_t));
157− state.color = 0x00000000;
158− state.flashMode = LIGHT_FLASH_NONE;
159− state.flashOnMS = 0;
160− state.flashOffMS = 0;
161− state.brightnessMode = 0;
162−
163− if (m_lightDevice->set_light(m_lightDevice, &state) != 0) {
164− qWarning() << "Failed to turn the light off";
165− }
166−}
plugins/Lights/Lights.h+4−8
@@ -22,8 +22,6 @@
2222#include <QtCore/QObject>
2323#include <QtGui/QColor>
2424
25−struct light_device_t;
26−
2725class Lights: public QObject
2826{
2927 Q_OBJECT
@@ -40,7 +38,6 @@ public:
4038 Q_ENUM(State)
4139
4240 explicit Lights(QObject *parent = 0);
43− ~Lights();
4441
4542 void setState(State newState);
4643 State state() const;
@@ -60,16 +57,15 @@ Q_SIGNALS:
6057 void onMillisecChanged(int onMs);
6158 void offMillisecChanged(int offMs);
6259
63−private:
64− light_device_t* m_lightDevice;
60+protected:
6561 QColor m_color;
6662 State m_state;
6763 int m_onMs;
6864 int m_offMs;
6965
70− bool init();
71− void turnOff();
72− void turnOn();
66+ virtual bool init() = 0;
67+ virtual void turnOff() = 0;
68+ virtual void turnOn() = 0;
7369};
7470
7571#endif
plugins/Lights/plugin.cpp+20−1
@@ -18,6 +18,11 @@
1818
1919#include "plugin.h"
2020#include "Lights.h"
21+#include "HfdLights.h"
22+#include "LegacyLights.h"
23+
24+// libandroid-properties
25+#include <hybris/properties/properties.h>
2126
2227#include <QtQml/qqml.h>
2328
@@ -25,7 +30,21 @@ static QObject *lights_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
2530{
2631 Q_UNUSED(engine)
2732 Q_UNUSED(scriptEngine)
28− return new Lights();
33+
34+ bool use_hfd = false;
35+ char buffer[PROP_VALUE_MAX];
36+ auto result = property_get("ro.build.version.sdk", buffer, "0");
37+ if (result) {
38+ auto sdkVersion = QString(buffer).toInt();
39+ if (sdkVersion >= 27 || sdkVersion == 0) {
40+ use_hfd = true;
41+ }
42+ }
43+ if (use_hfd) {
44+ return new HfdLights();
45+ } else {
46+ return new LegacyLights();
47+ }
2948}
3049
3150void LightsPlugin::registerTypes(const char *uri)
plugins/WindowManager/TopLevelWindowModel.cpp+4−3
@@ -317,10 +317,11 @@ void TopLevelWindowModel::onSurfaceDied(unityapi::MirSurfaceInterface *surface)
317317
318318 auto application = m_windowModel[i].application;
319319
320− // can't be starting if it already has a surface
321− Q_ASSERT(application->state() != unityapi::ApplicationInfoInterface::Starting);
320+ DEBUG_MSG << " application->name()=" << application->name()
321+ << " application->state()=" << application->state();
322322
323− if (application->state() == unityapi::ApplicationInfoInterface::Running) {
323+ if (application->state() == unityapi::ApplicationInfoInterface::Running
324+ || application->state() == unityapi::ApplicationInfoInterface::Starting) {
324325 m_windowModel[i].removeOnceSurfaceDestroyed = true;
325326 } else {
326327 // assume it got killed by the out-of-memory daemon.
po/am.po+4−6
@@ -8,10 +8,10 @@ msgstr ""
88"Project-Id-Version: unity8\n"
99"Report-Msgid-Bugs-To: \n"
1010"POT-Creation-Date: 2021-01-29 11:42-0600\n"
11−"PO-Revision-Date: 2021-01-31 01:03+0000\n"
11+"PO-Revision-Date: 2021-08-29 20:18+0000\n"
1212"Last-Translator: Samson <sambelet@yahoo.com>\n"
13−"Language-Team: Amharic <https://translate.ubports.com/projects/unity8/unity8/"
14−"am/>\n"
13+"Language-Team: Amharic <https://translate.ubports.com/projects/ubports/"
14+"lomiri/am/>\n"
1515"Language: am\n"
1616"MIME-Version: 1.0\n"
1717"Content-Type: text/plain; charset=UTF-8\n"
@@ -131,8 +131,6 @@ msgid "Restart"
131131msgstr "እንደገና ማስጀመሪያ"
132132
133133#: qml/Components/Dialogs.qml:276
134−#, fuzzy
135−#| msgid "Takes a screenshot."
136134msgid "Screenshot"
137135msgstr "መመልከቻ ፎቶ ማንሻ"
138136
@@ -142,7 +140,7 @@ msgstr "የ ፊደል ገበታ አቋራጮች"
142140
143141#: qml/Components/KeyboardShortcutsOverlay.qml:55
144142msgid "Lomiri"
145−msgstr ""
143+msgstr "Lomiri"
146144
147145#: qml/Components/KeyboardShortcutsOverlay.qml:62
148146msgid "PrtScr"
po/ar.po+20−25
@@ -8,9 +8,9 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: \n"
1010"POT-Creation-Date: 2021-01-29 11:42-0600\n"
11−"PO-Revision-Date: 2020-12-07 19:09+0000\n"
12−"Last-Translator: abidin <abidin24@tutanota.com>\n"
13−"Language-Team: Arabic <https://translate.ubports.com/projects/unity8/unity8/"
11+"PO-Revision-Date: 2021-10-12 07:49+0000\n"
12+"Last-Translator: HEROBRINE7GAMER <tapermeerkat77@gmail.com>\n"
13+"Language-Team: Arabic <https://translate.ubports.com/projects/ubports/lomiri/"
1414"ar/>\n"
1515"Language: ar\n"
1616"MIME-Version: 1.0\n"
@@ -39,7 +39,7 @@ msgstr "كلمة المرور غير صحيحة، الرجاء المحاولة
3939
4040#: plugins/LightDM/Greeter.cpp:270
4141msgid "Log In"
42−msgstr "لِج"
42+msgstr "سجل الدخول"
4343
4444#: plugins/LightDM/Greeter.cpp:270
4545msgid "Retry"
@@ -47,7 +47,7 @@ msgstr "حاول مجدداً"
4747
4848#: plugins/LightDM/UsersModel.cpp:157
4949msgid "Login"
50−msgstr "لِج"
50+msgstr "سجل الدخول"
5151
5252#: plugins/LightDM/UsersModel.cpp:165
5353msgid "Guest Session"
@@ -73,7 +73,7 @@ msgstr "اخرج"
7373
7474#: qml/Components/Dialogs.qml:177
7575msgid "Are you sure you want to log out?"
76−msgstr "أمتأكد أنك تريد الخروج ؟"
76+msgstr "أمتأكد أنك تريد تسجيل الخروج ؟"
7777
7878#: qml/Components/Dialogs.qml:180
7979msgctxt "Button: Lock the system"
@@ -132,10 +132,8 @@ msgid "Restart"
132132msgstr "أعد التشغيل"
133133
134134#: qml/Components/Dialogs.qml:276
135−#, fuzzy
136−#| msgid "Takes a screenshot."
137135msgid "Screenshot"
138−msgstr "خُذ لقطة للشاشة."
136+msgstr "لقطة شاشة"
139137
140138#: qml/Components/KeyboardShortcutsOverlay.qml:41
141139msgid "Keyboard Shortcuts"
@@ -143,7 +141,7 @@ msgstr "اختصارات لوحة المفاتيح"
143141
144142#: qml/Components/KeyboardShortcutsOverlay.qml:55
145143msgid "Lomiri"
146−msgstr ""
144+msgstr "لوميري"
147145
148146#: qml/Components/KeyboardShortcutsOverlay.qml:62
149147msgid "PrtScr"
@@ -187,7 +185,7 @@ msgstr "شغِل الطرفية."
187185
188186#: qml/Components/KeyboardShortcutsOverlay.qml:131
189187msgid "Launcher"
190−msgstr "مُطْلِق"
188+msgstr "مطلق"
191189
192190#: qml/Components/KeyboardShortcutsOverlay.qml:138
193191msgid "Super (Hold)"
@@ -202,9 +200,8 @@ msgid "Alt + F1"
202200msgstr "Alt + F1"
203201
204202#: qml/Components/KeyboardShortcutsOverlay.qml:156
205−#, fuzzy
206203msgid "Opens launcher keyboard navigation mode."
207−msgstr "افتح وضع التصفح باستخدام لوحة مفاتيح المطلق."
204+msgstr "فتح وضع التنقل بلوحة المفاتيح المشغلة."
208205
209206#: qml/Components/KeyboardShortcutsOverlay.qml:164
210207msgid "Super + Tab"
@@ -351,7 +348,6 @@ msgid "Close all"
351348msgstr "أغلق الكل"
352349
353350#: qml/Components/SharingPicker.qml:54
354−#, fuzzy
355351msgid "Preview Share Item"
356352msgstr "معاينة الملف المشارك"
357353
@@ -424,7 +420,7 @@ msgstr "طوارئ"
424420
425421#: qml/Greeter/PromptList.qml:126
426422msgid "Passphrase"
427−msgstr "العبارة السرية"
423+msgstr "كلمة السر"
428424
429425#: qml/Greeter/PromptList.qml:126
430426msgid "Passcode"
@@ -440,11 +436,11 @@ msgstr "ابحث…"
440436
441437#: qml/Launcher/PullToRefreshScopeStyle.qml:56
442438msgid "Pull to refresh…"
443−msgstr "اسحب للتحديث..."
439+msgstr "اسحب للتحديث…"
444440
445441#: qml/Launcher/PullToRefreshScopeStyle.qml:61
446442msgid "Release to refresh…"
447−msgstr "حرّر للتحديث..."
443+msgstr "حرّر للتحديث…"
448444
449445#: qml/Notifications/NotificationMenuItemFactory.qml:124
450446msgid "Show password"
@@ -477,9 +473,9 @@ msgstr[5] "%1 ساعة"
477473#, qt-format
478474msgid "%1 minute"
479475msgid_plural "%1 minutes"
480−msgstr[0] "أقل من دقيقة (%1)"
481−msgstr[1] "دقيقة واحدة (%1)"
482−msgstr[2] "دقيقتان (%1)"
476+msgstr[0] "%1 دقيقة"
477+msgstr[1] "دقيقة واحدة"
478+msgstr[2] "دقيقتان"
483479msgstr[3] "%1 دقائق"
484480msgstr[4] "%1 دقيقة"
485481msgstr[5] "%1 دقيقة"
@@ -541,7 +537,6 @@ msgid "Reply"
541537msgstr "رد"
542538
543539#: qml/Panel/Indicators/MessageMenuItemFactory.qml:209
544−#, fuzzy
545540msgctxt "Button: Call back on phone"
546541msgid "Call back"
547542msgstr "عاود الاتصال"
@@ -630,11 +625,11 @@ msgstr "تخطيط لوحة المفاتيح"
630625#: qml/Wizard/Pages/60-account.qml:72 qml/Wizard/Pages/79-system-update.qml:144
631626#: qml/Wizard/Pages/sim.qml:101
632627msgid "Skip"
633−msgstr "تخطى"
628+msgstr "تخط"
634629
635630#: qml/Wizard/Pages/30-wifi.qml:31
636631msgid "Connect to Wi‑Fi"
637−msgstr "اتصل بالواي فاي"
632+msgstr "اتصل بالإنترنت"
638633
639634#: qml/Wizard/Pages/30-wifi.qml:139
640635msgid "Connected"
@@ -735,7 +730,7 @@ msgstr "ابدأ"
735730#: qml/Wizard/Pages/passcode-confirm.qml:43
736731#: qml/Wizard/Pages/passcode-desktop.qml:104
737732msgid "Confirm passcode"
738−msgstr "تأكيد الرمز السري"
733+msgstr "تأكيد كلمة السر"
739734
740735#: qml/Wizard/Pages/passcode-confirm.qml:45
741736msgid "Incorrect passcode."
@@ -757,7 +752,7 @@ msgstr "أدخل 4 أرقام لإعداد الرمز السري"
757752#: qml/Wizard/Pages/passcode-desktop.qml:84
758753#: qml/Wizard/Pages/passcode-set.qml:54
759754msgid "Choose passcode"
760−msgstr "اختر الرمز السري"
755+msgstr "اختر كلمة السر"
761756
762757#: qml/Wizard/Pages/passcode-desktop.qml:139
763758msgid "Passcode too short"
po/cs.po+12−12
@@ -8,7 +8,7 @@ msgstr ""
88"Project-Id-Version: unity8\n"
99"Report-Msgid-Bugs-To: \n"
1010"POT-Creation-Date: 2021-01-29 11:42-0600\n"
11−"PO-Revision-Date: 2021-04-30 20:44+0000\n"
11+"PO-Revision-Date: 2021-12-05 18:44+0000\n"
1212"Last-Translator: Milan Korecký <milan.korecky@gmail.com>\n"
1313"Language-Team: Czech <https://translate.ubports.com/projects/ubports/lomiri/"
1414"cs/>\n"
@@ -30,11 +30,11 @@ msgstr "Uživatelské jméno"
3030
3131#: plugins/LightDM/Greeter.cpp:258
3232msgid "Failed to authenticate"
33−msgstr "Chyba při ověřování se"
33+msgstr "Chyba při ověřování"
3434
3535#: plugins/LightDM/Greeter.cpp:260
3636msgid "Invalid password, please try again"
37−msgstr "Nesprávné heslo – zkuste to znovu prosím"
37+msgstr "Nesprávné heslo, zkuste to znovu prosím"
3838
3939#: plugins/LightDM/Greeter.cpp:270
4040msgid "Log In"
@@ -42,11 +42,11 @@ msgstr "Přihlásit se"
4242
4343#: plugins/LightDM/Greeter.cpp:270
4444msgid "Retry"
45−msgstr "Znovu"
45+msgstr "Zkusit znovu"
4646
4747#: plugins/LightDM/UsersModel.cpp:157
4848msgid "Login"
49−msgstr "Zahájit relaci"
49+msgstr "Přihlášení"
5050
5151#: plugins/LightDM/UsersModel.cpp:165
5252msgid "Guest Session"
@@ -55,7 +55,7 @@ msgstr "Sezení hosta"
5555#: plugins/Unity/Launcher/launcheritem.cpp:48
5656#: plugins/Unity/Launcher/launcheritem.cpp:122
5757msgid "Pin shortcut"
58−msgstr "Připnout zástupce"
58+msgstr "Připnout odkaz"
5959
6060#: plugins/Unity/Launcher/launcheritem.cpp:54
6161msgid "Quit"
@@ -63,7 +63,7 @@ msgstr "Ukončit"
6363
6464#: plugins/Unity/Launcher/launcheritem.cpp:122
6565msgid "Unpin shortcut"
66−msgstr "Zrušit připnutí zástupce"
66+msgstr "Zrušit připnutý odkaz"
6767
6868#: qml/Components/Dialogs.qml:176
6969msgctxt "Title: Lock/Log out dialog"
@@ -308,7 +308,7 @@ msgstr "Tísňové volání"
308308
309309#: qml/Components/Lockscreen.qml:245
310310msgid "OK"
311−msgstr "Přijmout"
311+msgstr "OK"
312312
313313#: qml/Components/MediaServices/VideoPlayerControls.qml:44
314314#, qt-format
@@ -481,8 +481,8 @@ msgstr[2] "%1 minuty"
481481msgid "%1 second"
482482msgid_plural "%1 seconds"
483483msgstr[0] "%1 sekunda"
484−msgstr[1] "%1 sekundy"
485−msgstr[2] "%1 sekund"
484+msgstr[1] "%1 sekund"
485+msgstr[2] ""
486486
487487#: qml/Panel/Indicators/IndicatorMenuItemFactory.qml:1009
488488msgid "0 seconds"
@@ -512,7 +512,7 @@ msgstr "Zrušeno"
512512
513513#: qml/Panel/Indicators/IndicatorMenuItemFactory.qml:1027
514514msgid "Finished"
515−msgstr "Dokončeno"
515+msgstr "Hotovo"
516516
517517#: qml/Panel/Indicators/IndicatorMenuItemFactory.qml:1029
518518msgid "Failed, tap to retry"
@@ -585,7 +585,7 @@ msgstr "Jazyk"
585585#: qml/Wizard/Pages/passcode-desktop.qml:154
586586#: qml/Wizard/Pages/password-set.qml:153
587587msgid "Next"
588−msgstr "Následující"
588+msgstr "Další"
589589
590590#: qml/Wizard/Pages/10-welcome-update.qml:91
591591msgid "Welcome to "
po/el.po+4−6
@@ -8,9 +8,9 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: \n"
1010"POT-Creation-Date: 2021-01-29 11:42-0600\n"
11−"PO-Revision-Date: 2020-08-19 00:03+0000\n"
11+"PO-Revision-Date: 2021-09-20 23:03+0000\n"
1212"Last-Translator: Efstathios Iosifidis <iefstathios@gmail.com>\n"
13−"Language-Team: Greek <https://translate.ubports.com/projects/unity8/unity8/"
13+"Language-Team: Greek <https://translate.ubports.com/projects/ubports/lomiri/"
1414"el/>\n"
1515"Language: el\n"
1616"MIME-Version: 1.0\n"
@@ -131,10 +131,8 @@ msgid "Restart"
131131msgstr "Επανεκκίνηση"
132132
133133#: qml/Components/Dialogs.qml:276
134−#, fuzzy
135−#| msgid "Takes a screenshot."
136134msgid "Screenshot"
137−msgstr "Λαμβάνει ένα στιγμιότυπο οθόνης."
135+msgstr "Στιγμιότυπο οθόνης"
138136
139137#: qml/Components/KeyboardShortcutsOverlay.qml:41
140138msgid "Keyboard Shortcuts"
@@ -142,7 +140,7 @@ msgstr "Συντομεύσεις Πληκτρολογίου"
142140
143141#: qml/Components/KeyboardShortcutsOverlay.qml:55
144142msgid "Lomiri"
145−msgstr ""
143+msgstr "Lomiri"
146144
147145#: qml/Components/KeyboardShortcutsOverlay.qml:62
148146msgid "PrtScr"
po/es.po+22−22
@@ -8,8 +8,8 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: \n"
1010"POT-Creation-Date: 2021-01-29 11:42-0600\n"
11−"PO-Revision-Date: 2021-03-31 05:24+0000\n"
12−"Last-Translator: Josué <j2g2rp@gmail.com>\n"
11+"PO-Revision-Date: 2021-10-04 12:20+0000\n"
12+"Last-Translator: Adolfo Jayme Barrientos <fitojb@ubuntu.com>\n"
1313"Language-Team: Spanish <https://translate.ubports.com/projects/ubports/"
1414"lomiri/es/>\n"
1515"Language: es\n"
@@ -34,11 +34,11 @@ msgstr "Fallo al autenticar"
3434
3535#: plugins/LightDM/Greeter.cpp:260
3636msgid "Invalid password, please try again"
37−msgstr "Contraseña incorrecta, por favor, inténtelo de nuevo"
37+msgstr "Contraseña incorrecta; inténtelo de nuevo"
3838
3939#: plugins/LightDM/Greeter.cpp:270
4040msgid "Log In"
41−msgstr "Iniciar sesión"
41+msgstr "Acceder"
4242
4343#: plugins/LightDM/Greeter.cpp:270
4444msgid "Retry"
@@ -46,7 +46,7 @@ msgstr "Reintentar"
4646
4747#: plugins/LightDM/UsersModel.cpp:157
4848msgid "Login"
49−msgstr "Iniciar sesión"
49+msgstr "Acceder"
5050
5151#: plugins/LightDM/UsersModel.cpp:165
5252msgid "Guest Session"
@@ -68,7 +68,7 @@ msgstr "Desanclar atajo"
6868#: qml/Components/Dialogs.qml:176
6969msgctxt "Title: Lock/Log out dialog"
7070msgid "Log out"
71−msgstr "Cerrar la sesión"
71+msgstr "Cerrar sesión"
7272
7373#: qml/Components/Dialogs.qml:177
7474msgid "Are you sure you want to log out?"
@@ -82,7 +82,7 @@ msgstr "Bloquear"
8282#: qml/Components/Dialogs.qml:191
8383msgctxt "Button: Log out from the system"
8484msgid "Log Out"
85−msgstr "Cerrar la sesión"
85+msgstr "Cerrar sesión"
8686
8787#: qml/Components/Dialogs.qml:199 qml/Components/Dialogs.qml:284
8888#: qml/Greeter/NarrowView.qml:215 qml/Wizard/Pages/passcode-confirm.qml:32
@@ -164,7 +164,7 @@ msgstr "Super + Espacio"
164164
165165#: qml/Components/KeyboardShortcutsOverlay.qml:93
166166msgid "Switches to next keyboard layout."
167−msgstr "Cambia a la siguiente disposición de teclado."
167+msgstr "Cambia a la disposición de teclado siguiente."
168168
169169#: qml/Components/KeyboardShortcutsOverlay.qml:101
170170msgid "Super + Shift + Space"
@@ -172,7 +172,7 @@ msgstr "Super + Mayús + Espacio"
172172
173173#: qml/Components/KeyboardShortcutsOverlay.qml:106
174174msgid "Switches to previous keyboard layout."
175−msgstr "Cambia a la anterior disposición de teclado."
175+msgstr "Cambia a la disposición de teclado anterior."
176176
177177#: qml/Components/KeyboardShortcutsOverlay.qml:114
178178msgid "Ctrl + Alt + T"
@@ -184,15 +184,15 @@ msgstr "Abre la aplicación Terminal."
184184
185185#: qml/Components/KeyboardShortcutsOverlay.qml:131
186186msgid "Launcher"
187−msgstr "Lanzador"
187+msgstr "Iniciador"
188188
189189#: qml/Components/KeyboardShortcutsOverlay.qml:138
190190msgid "Super (Hold)"
191−msgstr "Super (Mantener)"
191+msgstr "Súper (mantener)"
192192
193193#: qml/Components/KeyboardShortcutsOverlay.qml:143
194194msgid "Opens the launcher, displays shortcuts."
195−msgstr "Abre el lanzador y muestra los atajos."
195+msgstr "Abre el iniciador y muestra los atajos."
196196
197197#: qml/Components/KeyboardShortcutsOverlay.qml:151
198198msgid "Alt + F1"
@@ -200,7 +200,7 @@ msgstr "Alt + F1"
200200
201201#: qml/Components/KeyboardShortcutsOverlay.qml:156
202202msgid "Opens launcher keyboard navigation mode."
203−msgstr "Abre el Lanzador en modo de navegación de teclado."
203+msgstr "Abre el iniciador en modo de navegación de teclado."
204204
205205#: qml/Components/KeyboardShortcutsOverlay.qml:164
206206msgid "Super + Tab"
@@ -208,7 +208,7 @@ msgstr "Super + Tab"
208208
209209#: qml/Components/KeyboardShortcutsOverlay.qml:169
210210msgid "Switches applications via the launcher."
211−msgstr "Recorre las aplicaciones del Lanzador."
211+msgstr "Cambia de aplicación mediante el iniciador."
212212
213213#: qml/Components/KeyboardShortcutsOverlay.qml:177
214214msgid "Super + 1 to 0"
@@ -216,7 +216,7 @@ msgstr "Súper + 1 a 0"
216216
217217#: qml/Components/KeyboardShortcutsOverlay.qml:182
218218msgid "Same as clicking on a launcher icon."
219−msgstr "Lo mismo que hacer clic en un icono del Lanzador."
219+msgstr "Lo mismo que pulsar en un icono del iniciador."
220220
221221#: qml/Components/KeyboardShortcutsOverlay.qml:190
222222msgid "Super + A"
@@ -288,7 +288,7 @@ msgstr "Ctrl + Super + Arriba o Derecha"
288288
289289#: qml/Components/KeyboardShortcutsOverlay.qml:336
290290msgid "Semi-maximizes the current window."
291−msgstr "Semi-maximiza la ventana actual."
291+msgstr "Semimaximiza la ventana actual."
292292
293293#: qml/Components/KeyboardShortcutsOverlay.qml:344
294294msgid "Alt + F4"
@@ -373,7 +373,7 @@ msgstr "Deslice con dos dedos para desplazarse."
373373
374374#: qml/Components/VirtualTouchPad.qml:396
375375msgid "Find more settings in the system settings."
376−msgstr "Encuentre mas ajustes en la configuración del sistema."
376+msgstr "Encuentre más ajustes en Configuración del sistema."
377377
378378#: qml/Greeter/CoverPage.qml:127
379379msgid "Unlock"
@@ -549,21 +549,21 @@ msgstr ""
549549
550550#: qml/Tutorial/TutorialLeft.qml:47
551551msgid "Short swipe from the left edge to open the launcher"
552−msgstr "Deslizar un poco desde el borde izquierdo para abrir el lanzador"
552+msgstr "Deslice un poco desde el borde izquierdo para abrir el iniciador"
553553
554554#: qml/Tutorial/TutorialRight.qml:54
555555msgid "Push your mouse against the right edge to view your open apps"
556556msgstr ""
557−"Para ver las aplicaciones abiertas empuja el ratón contra el borde derecho"
557+"Para ver las aplicaciones abiertas empuje el ratón contra el borde derecho"
558558
559559#: qml/Tutorial/TutorialRight.qml:55
560560msgid "Swipe from the right edge to view your open apps"
561−msgstr "Deslizar desde el borde derecho para ver las aplis abiertas"
561+msgstr "Deslice desde el borde derecho para ver las aplicaciones abiertas"
562562
563563#: qml/Tutorial/TutorialTop.qml:53
564564msgid "Swipe from the top edge to access notifications and quick settings"
565565msgstr ""
566−"Deslizar desde el borde superior para acceder a las notificaciones y "
566+"Deslice desde el borde superior para acceder a las notificaciones y "
567567"configuración"
568568
569569#: qml/Wizard/Page.qml:59
@@ -583,7 +583,7 @@ msgstr "Idioma"
583583#: qml/Wizard/Pages/passcode-desktop.qml:154
584584#: qml/Wizard/Pages/password-set.qml:153
585585msgid "Next"
586−msgstr "Siguiente"
586+msgstr "Sig."
587587
588588#: qml/Wizard/Pages/10-welcome-update.qml:91
589589msgid "Welcome to "
po/he.po+9−11
@@ -8,9 +8,9 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: \n"
1010"POT-Creation-Date: 2021-01-29 11:42-0600\n"
11−"PO-Revision-Date: 2020-09-05 22:10+0000\n"
12−"Last-Translator: tamer dab <dabsantamer@yahoo.com>\n"
13−"Language-Team: Hebrew <https://translate.ubports.com/projects/unity8/unity8/"
11+"PO-Revision-Date: 2022-02-07 20:15+0000\n"
12+"Last-Translator: anonymous <noreply@weblate.org>\n"
13+"Language-Team: Hebrew <https://translate.ubports.com/projects/ubports/lomiri/"
1414"he/>\n"
1515"Language: he\n"
1616"MIME-Version: 1.0\n"
@@ -129,10 +129,8 @@ msgid "Restart"
129129msgstr "הפעלה מחדש"
130130
131131#: qml/Components/Dialogs.qml:276
132−#, fuzzy
133−#| msgid "Takes a screenshot."
134132msgid "Screenshot"
135−msgstr "מצלם מסך."
133+msgstr "צילום מסך"
136134
137135#: qml/Components/KeyboardShortcutsOverlay.qml:41
138136msgid "Keyboard Shortcuts"
@@ -140,7 +138,7 @@ msgstr "קיצורי דרך במקלדת"
140138
141139#: qml/Components/KeyboardShortcutsOverlay.qml:55
142140msgid "Lomiri"
143−msgstr ""
141+msgstr "Lomiri"
144142
145143#: qml/Components/KeyboardShortcutsOverlay.qml:62
146144msgid "PrtScr"
@@ -458,13 +456,13 @@ msgstr "שום דבר לא מתנגן"
458456msgid "%1 hour"
459457msgid_plural "%1 hours"
460458msgstr[0] "%1 שעה"
461−msgstr[1] "%1 שעות"
459+msgstr[1] "%1 שעת"
462460
463461#: qml/Panel/Indicators/IndicatorMenuItemFactory.qml:1001
464462#, qt-format
465463msgid "%1 minute"
466464msgid_plural "%1 minutes"
467−msgstr[0] "דקה %1"
465+msgstr[0] "%1 דקה"
468466msgstr[1] "%1 דקות"
469467
470468#: qml/Panel/Indicators/IndicatorMenuItemFactory.qml:1006
@@ -526,7 +524,7 @@ msgstr "התקשר חזרה"
526524
527525#: qml/Panel/PanelMenuPage.qml:92
528526msgid "Back"
529−msgstr "חזור"
527+msgstr "חזרה"
530528
531529#: qml/Stage/SideStage.qml:76
532530msgid "Drag using 3 fingers any application from one window to the other"
@@ -559,7 +557,7 @@ msgstr "החלק מהקצה העליון כדי לגשת להתראות ולהג
559557#: qml/Wizard/Page.qml:59
560558msgctxt "Button: Go back one page in the Wizard"
561559msgid "Back"
562−msgstr "חזור"
560+msgstr "חזרה"
563561
564562#: qml/Wizard/Pages/10-welcome.qml:29
565563msgid "Language"
po/hu.po+9−11
@@ -8,8 +8,8 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: \n"
1010"POT-Creation-Date: 2021-01-29 11:42-0600\n"
11−"PO-Revision-Date: 2021-03-17 10:43+0000\n"
12−"Last-Translator: David Farkas <dvdfrks@gmail.com>\n"
11+"PO-Revision-Date: 2022-02-06 00:18+0000\n"
12+"Last-Translator: Lundrin <berenyi.vilmos@tutanota.com>\n"
1313"Language-Team: Hungarian <https://translate.ubports.com/projects/ubports/"
1414"lomiri/hu/>\n"
1515"Language: hu\n"
@@ -88,7 +88,7 @@ msgstr "Kijelentkezés"
8888#: qml/Greeter/NarrowView.qml:215 qml/Wizard/Pages/passcode-confirm.qml:32
8989#: qml/Wizard/Pages/passcode-set.qml:32
9090msgid "Cancel"
91−msgstr "Mégsem"
91+msgstr "Mégse"
9292
9393#: qml/Components/Dialogs.qml:211
9494msgctxt "Title: Reboot dialog"
@@ -131,10 +131,8 @@ msgid "Restart"
131131msgstr "Újraindítás"
132132
133133#: qml/Components/Dialogs.qml:276
134−#, fuzzy
135−#| msgid "Takes a screenshot."
136134msgid "Screenshot"
137−msgstr "Képernyőkép készítése."
135+msgstr "Képernyőmentés"
138136
139137#: qml/Components/KeyboardShortcutsOverlay.qml:41
140138msgid "Keyboard Shortcuts"
@@ -142,7 +140,7 @@ msgstr "Gyorsbillentyűk"
142140
143141#: qml/Components/KeyboardShortcutsOverlay.qml:55
144142msgid "Lomiri"
145−msgstr ""
143+msgstr "Lomiri"
146144
147145#: qml/Components/KeyboardShortcutsOverlay.qml:62
148146msgid "PrtScr"
@@ -506,7 +504,7 @@ msgstr "Megszakítva"
506504
507505#: qml/Panel/Indicators/IndicatorMenuItemFactory.qml:1027
508506msgid "Finished"
509−msgstr "Befejeződött"
507+msgstr "Kész"
510508
511509#: qml/Panel/Indicators/IndicatorMenuItemFactory.qml:1029
512510msgid "Failed, tap to retry"
@@ -578,7 +576,7 @@ msgstr "Nyelv"
578576#: qml/Wizard/Pages/passcode-desktop.qml:154
579577#: qml/Wizard/Pages/password-set.qml:153
580578msgid "Next"
581−msgstr "Következő"
579+msgstr "Köv."
582580
583581#: qml/Wizard/Pages/10-welcome-update.qml:91
584582msgid "Welcome to "
@@ -620,7 +618,7 @@ msgstr "Csatlakozás Wi-Fi hálózathoz"
620618
621619#: qml/Wizard/Pages/30-wifi.qml:139
622620msgid "Connected"
623−msgstr "Kapcsolódva"
621+msgstr "Csatlakozva"
624622
625623#: qml/Wizard/Pages/30-wifi.qml:172
626624msgid "Available Wi-Fi networks"
@@ -741,7 +739,7 @@ msgstr "Adjon meg egy 4 számjegyű jelkódot"
741739#: qml/Wizard/Pages/passcode-desktop.qml:84
742740#: qml/Wizard/Pages/passcode-set.qml:54
743741msgid "Choose passcode"
744−msgstr "Jelkód megadása"
742+msgstr "Válasszon jelkódot"
745743
746744#: qml/Wizard/Pages/passcode-desktop.qml:139
747745msgid "Passcode too short"
po/it.po+6−6
@@ -8,8 +8,8 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: \n"
1010"POT-Creation-Date: 2021-01-29 11:42-0600\n"
11−"PO-Revision-Date: 2021-07-18 22:26+0000\n"
12−"Last-Translator: Mike <miguel2000@livecom.it>\n"
11+"PO-Revision-Date: 2022-01-22 21:58+0000\n"
12+"Last-Translator: Matteo <mattbel10@hotmail.com>\n"
1313"Language-Team: Italian <https://translate.ubports.com/projects/ubports/"
1414"lomiri/it/>\n"
1515"Language: it\n"
@@ -409,7 +409,7 @@ msgstr "Riprova"
409409
410410#: qml/Greeter/Greeter.qml:551
411411msgid "Enter passphrase to unlock"
412−msgstr "Inserire passphrase per sbloccare"
412+msgstr "Inserire parola chiave per sbloccare"
413413
414414#: qml/Greeter/Greeter.qml:552
415415msgid "Enter passcode to unlock"
@@ -421,7 +421,7 @@ msgstr "Emergenza"
421421
422422#: qml/Greeter/PromptList.qml:126
423423msgid "Passphrase"
424−msgstr "Passphrase"
424+msgstr "Parola chiave"
425425
426426#: qml/Greeter/PromptList.qml:126
427427msgid "Passcode"
@@ -540,12 +540,12 @@ msgstr "Trascinare con 3 dita qualsiasi applicazione da una finestra all'altra"
540540
541541#: qml/Stage/Stage.qml:671
542542msgid "No running apps"
543−msgstr "Nessun applicazione in esecuzione"
543+msgstr "Nessuna applicazione in esecuzione"
544544
545545#: qml/Tutorial/TutorialLeftLong.qml:49
546546msgid "Long swipe from the left edge to open the Application Drawer"
547547msgstr ""
548−"Scorrere lentamente dal bordo sinistro per aprire il Drawer delle "
548+"Scorrere lentamente dal bordo sinistro per aprire il Cassetto delle "
549549"Applicazioni"
550550
551551#: qml/Tutorial/TutorialLeft.qml:47
po/km.po+11−14
@@ -8,16 +8,17 @@ msgstr ""
88"Project-Id-Version: unity8\n"
99"Report-Msgid-Bugs-To: \n"
1010"POT-Creation-Date: 2021-01-29 11:42-0600\n"
11−"PO-Revision-Date: 2014-08-20 02:48+0000\n"
12−"Last-Translator: Sophea Sok <sksophea@gmail.com>\n"
13−"Language-Team: Khmer <km@li.org>\n"
14−"Language: \n"
11+"PO-Revision-Date: 2022-03-14 13:38+0000\n"
12+"Last-Translator: ប៉ុកណូ រ៉ូយ៉ាល់ <poknoroyal@outlook.com>\n"
13+"Language-Team: Central Khmer <https://translate.ubports.com/projects/ubports/"
14+"lomiri/km/>\n"
15+"Language: km\n"
1516"MIME-Version: 1.0\n"
1617"Content-Type: text/plain; charset=UTF-8\n"
1718"Content-Transfer-Encoding: 8bit\n"
1819"Plural-Forms: nplurals=1; plural=0;\n"
20+"X-Generator: Weblate 3.11.3\n"
1921"X-Launchpad-Export-Date: 2016-11-23 06:35+0000\n"
20−"X-Generator: Launchpad (build 18269)\n"
2122
2223#: plugins/LightDM/Greeter.cpp:199
2324msgid "Password: "
@@ -25,7 +26,7 @@ msgstr "ពាក្យសម្ងាត់៖ "
2526
2627#: plugins/LightDM/Greeter.cpp:214
2728msgid "Username"
28−msgstr ""
29+msgstr "អត្ថនាម"
2930
3031#: plugins/LightDM/Greeter.cpp:258
3132msgid "Failed to authenticate"
@@ -375,7 +376,7 @@ msgstr ""
375376
376377#: qml/Greeter/CoverPage.qml:127
377378msgid "Unlock"
378−msgstr "ដោះ​សោ"
379+msgstr "ដោះសោ"
379380
380381#: qml/Greeter/DelayedLockscreen.qml:43
381382msgid "Device Locked"
@@ -523,11 +524,8 @@ msgid "Call back"
523524msgstr "ហៅ​មកវិញ"
524525
525526#: qml/Panel/PanelMenuPage.qml:92
526−#, fuzzy
527−#| msgctxt "Button: Go back one page in the Wizard"
528−#| msgid "Back"
529527msgid "Back"
530−msgstr "ថយក្រោយ"
528+msgstr "បកក្រោយ"
531529
532530#: qml/Stage/SideStage.qml:76
533531msgid "Drag using 3 fingers any application from one window to the other"
@@ -592,9 +590,8 @@ msgid "What's new"
592590msgstr ""
593591
594592#: qml/Wizard/Pages/11-changelog.qml:74
595−#, fuzzy
596593msgid "Loading..."
597−msgstr "កំពុង​និយាយ..."
594+msgstr "កំពុងផ្ទុក…"
598595
599596#: qml/Wizard/Pages/20-keyboard.qml:31
600597msgid "Select Keyboard"
@@ -606,7 +603,7 @@ msgstr ""
606603
607604#: qml/Wizard/Pages/20-keyboard.qml:93
608605msgid "Keyboard layout"
609−msgstr ""
606+msgstr "ប្លង់ក្ដារចុច"
610607
611608#: qml/Wizard/Pages/20-keyboard.qml:152 qml/Wizard/Pages/30-wifi.qml:214
612609#: qml/Wizard/Pages/60-account.qml:72 qml/Wizard/Pages/79-system-update.qml:144
po/nb.po+2−4
@@ -8,7 +8,7 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: \n"
1010"POT-Creation-Date: 2021-01-29 11:42-0600\n"
11−"PO-Revision-Date: 2021-07-22 23:44+0000\n"
11+"PO-Revision-Date: 2021-10-15 10:11+0000\n"
1212"Last-Translator: Allan Nordhøy <epost@anotheragency.no>\n"
1313"Language-Team: Norwegian Bokmål <https://translate.ubports.com/projects/"
1414"ubports/lomiri/nb/>\n"
@@ -131,10 +131,8 @@ msgid "Restart"
131131msgstr "Start på nytt"
132132
133133#: qml/Components/Dialogs.qml:276
134−#, fuzzy
135−#| msgid "Takes a screenshot."
136134msgid "Screenshot"
137−msgstr "Tar bilde av skjermen."
135+msgstr "Skjermavbildning"
138136
139137#: qml/Components/KeyboardShortcutsOverlay.qml:41
140138msgid "Keyboard Shortcuts"
po/nl.po+3−3
@@ -8,8 +8,8 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: \n"
1010"POT-Creation-Date: 2021-01-29 11:42-0600\n"
11−"PO-Revision-Date: 2021-07-22 10:09+0000\n"
12−"Last-Translator: Jeroen Baten <jbaten@i2rs.nl>\n"
11+"PO-Revision-Date: 2021-09-21 19:30+0000\n"
12+"Last-Translator: Heimen Stoffels <vistausss@fastmail.com>\n"
1313"Language-Team: Dutch <https://translate.ubports.com/projects/ubports/lomiri/"
1414"nl/>\n"
1515"Language: nl\n"
@@ -463,7 +463,7 @@ msgstr "Er wordt niets afgespeeld"
463463msgid "%1 hour"
464464msgid_plural "%1 hours"
465465msgstr[0] "%1 uur"
466−msgstr[1] "%1 uren"
466+msgstr[1] "%1 uur"
467467
468468#: qml/Panel/Indicators/IndicatorMenuItemFactory.qml:1001
469469#, qt-format
po/pt.po+4−4
@@ -8,8 +8,8 @@ msgstr ""
88"Project-Id-Version: unity8\n"
99"Report-Msgid-Bugs-To: \n"
1010"POT-Creation-Date: 2021-01-29 11:42-0600\n"
11−"PO-Revision-Date: 2021-06-01 14:51+0000\n"
12−"Last-Translator: Ivo Xavier <ivofernandes12@gmail.com>\n"
11+"PO-Revision-Date: 2021-11-19 22:39+0000\n"
12+"Last-Translator: Hugo Carvalho <hugokarvalho@hotmail.com>\n"
1313"Language-Team: Portuguese <https://translate.ubports.com/projects/ubports/"
1414"lomiri/pt/>\n"
1515"Language: pt\n"
@@ -531,7 +531,7 @@ msgstr "Ligar de volta"
531531
532532#: qml/Panel/PanelMenuPage.qml:92
533533msgid "Back"
534−msgstr "Retroceder"
534+msgstr "Voltar"
535535
536536#: qml/Stage/SideStage.qml:76
537537msgid "Drag using 3 fingers any application from one window to the other"
@@ -712,7 +712,7 @@ msgstr "Está pronto/a para usar o seu dispositivo"
712712
713713#: qml/Wizard/Pages/80-finished.qml:126
714714msgid "Continue"
715−msgstr "Avançar"
715+msgstr "Continuar"
716716
717717#: qml/Wizard/Pages/80-finished.qml:126
718718msgid "Get Started"
po/sc.po+6−6
@@ -8,7 +8,7 @@ msgstr ""
88"Project-Id-Version: unity8\n"
99"Report-Msgid-Bugs-To: \n"
1010"POT-Creation-Date: 2021-01-29 11:42-0600\n"
11−"PO-Revision-Date: 2021-04-22 10:44+0000\n"
11+"PO-Revision-Date: 2021-11-21 15:09+0000\n"
1212"Last-Translator: Adria <adriamartinmor@gmail.com>\n"
1313"Language-Team: Sardinian <https://translate.ubports.com/projects/ubports/"
1414"lomiri/sc/>\n"
@@ -539,7 +539,7 @@ msgstr "Traga cun 3 ditos is aplicatziones dae una ventana a s'àtera"
539539
540540#: qml/Stage/Stage.qml:671
541541msgid "No running apps"
542−msgstr "Peruna aplicatzione ativa"
542+msgstr "Nissuna aplicatzione ativa"
543543
544544#: qml/Tutorial/TutorialLeftLong.qml:49
545545msgid "Long swipe from the left edge to open the Application Drawer"
@@ -634,7 +634,7 @@ msgstr "Retes Wi-Fi a disponimentu"
634634
635635#: qml/Wizard/Pages/30-wifi.qml:173
636636msgid "No available Wi-Fi networks"
637−msgstr "Peruna rete Wi-Fi a disponimentu"
637+msgstr "Nissuna rete Wi-Fi a disponimentu"
638638
639639#: qml/Wizard/Pages/50-timezone.qml:30
640640msgid "Time Zone"
@@ -669,7 +669,7 @@ msgstr "Crea unu còdighe de atzessu (nùmeros isceti)"
669669#: qml/Wizard/Pages/70-passwd-type.qml:107
670670msgctxt "Label: Type of security method"
671671msgid "No lock code"
672−msgstr "Perunu còdighe de blocu"
672+msgstr "Nissunu còdighe de blocu"
673673
674674#: qml/Wizard/Pages/79-system-update.qml:28
675675msgid "Update Device"
@@ -696,7 +696,7 @@ msgstr "Versione %1"
696696
697697#: qml/Wizard/Pages/79-system-update.qml:95
698698msgid "This could take a few minutes..."
699−msgstr "B'at a pònnere carchi minutu..."
699+msgstr "Podet trigare calicunu minutu..."
700700
701701#: qml/Wizard/Pages/79-system-update.qml:109
702702msgid "Install and restart now"
@@ -783,7 +783,7 @@ msgstr "Cunfirma sa crae"
783783
784784#: qml/Wizard/Pages/sim.qml:27
785785msgid "No SIM card installed"
786−msgstr "Peruna SIM installada"
786+msgstr "Nissuna SIM installada"
787787
788788#: qml/Wizard/Pages/sim.qml:54
789789msgid "SIM card added"
po/sv.po+25−52
@@ -8,8 +8,8 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: \n"
1010"POT-Creation-Date: 2021-01-29 11:42-0600\n"
11−"PO-Revision-Date: 2021-07-26 20:33+0000\n"
12−"Last-Translator: Rondy Andersson <rondarius@vivaldi.net>\n"
11+"PO-Revision-Date: 2021-10-19 20:55+0000\n"
12+"Last-Translator: Jakob Fridesjö <jakob.fridesjo@tuta.io>\n"
1313"Language-Team: Swedish <https://translate.ubports.com/projects/ubports/"
1414"lomiri/sv/>\n"
1515"Language: sv\n"
@@ -26,15 +26,15 @@ msgstr "Lösenord: "
2626
2727#: plugins/LightDM/Greeter.cpp:214
2828msgid "Username"
29−msgstr ""
29+msgstr "Användarnamn"
3030
3131#: plugins/LightDM/Greeter.cpp:258
3232msgid "Failed to authenticate"
33−msgstr ""
33+msgstr "Misslyckades att autentisera"
3434
3535#: plugins/LightDM/Greeter.cpp:260
3636msgid "Invalid password, please try again"
37−msgstr ""
37+msgstr "Felaktigt lösenord, vänligen försök igen"
3838
3939#: plugins/LightDM/Greeter.cpp:270
4040msgid "Log In"
@@ -46,11 +46,11 @@ msgstr "Försök igen"
4646
4747#: plugins/LightDM/UsersModel.cpp:157
4848msgid "Login"
49−msgstr ""
49+msgstr "Inloggning"
5050
5151#: plugins/LightDM/UsersModel.cpp:165
5252msgid "Guest Session"
53−msgstr ""
53+msgstr "Gästsession"
5454
5555#: plugins/Unity/Launcher/launcheritem.cpp:48
5656#: plugins/Unity/Launcher/launcheritem.cpp:122
@@ -131,10 +131,8 @@ msgid "Restart"
131131msgstr "Starta om"
132132
133133#: qml/Components/Dialogs.qml:276
134−#, fuzzy
135−#| msgid "Takes a screenshot."
136134msgid "Screenshot"
137−msgstr "Tar en skärmbild."
135+msgstr "Skärmdump"
138136
139137#: qml/Components/KeyboardShortcutsOverlay.qml:41
140138msgid "Keyboard Shortcuts"
@@ -142,7 +140,7 @@ msgstr "Tangentbordsgenvägar"
142140
143141#: qml/Components/KeyboardShortcutsOverlay.qml:55
144142msgid "Lomiri"
145−msgstr ""
143+msgstr "Lomiri"
146144
147145#: qml/Components/KeyboardShortcutsOverlay.qml:62
148146msgid "PrtScr"
@@ -157,10 +155,8 @@ msgid "Alt + PrtScr"
157155msgstr "Alt + PrtScr"
158156
159157#: qml/Components/KeyboardShortcutsOverlay.qml:80
160−#, fuzzy
161−#| msgid "Takes a screenshot of a window."
162158msgid "Takes a screenshot of the current window."
163−msgstr "Tar en skärmbild av ett fönster."
159+msgstr "Tar en skärmdump av det nuvarande fönstret."
164160
165161#: qml/Components/KeyboardShortcutsOverlay.qml:88
166162msgid "Super + Space"
@@ -179,16 +175,12 @@ msgid "Switches to previous keyboard layout."
179175msgstr "Byter till tidigare tangentbordslayout."
180176
181177#: qml/Components/KeyboardShortcutsOverlay.qml:114
182−#, fuzzy
183−#| msgid "Ctrl + Super + D"
184178msgid "Ctrl + Alt + T"
185−msgstr "Ctrl + Super + D"
179+msgstr "Ctrl + Alt + T"
186180
187181#: qml/Components/KeyboardShortcutsOverlay.qml:119
188−#, fuzzy
189−#| msgid "Switches between applications."
190182msgid "Starts the Terminal application."
191−msgstr "Växlar mellan program."
183+msgstr "Startar terminalen."
192184
193185#: qml/Components/KeyboardShortcutsOverlay.qml:131
194186msgid "Launcher"
@@ -219,26 +211,20 @@ msgid "Switches applications via the launcher."
219211msgstr "Byter program via Programstartaren."
220212
221213#: qml/Components/KeyboardShortcutsOverlay.qml:177
222−#, fuzzy
223−#| msgid "Super + 0 to 9"
224214msgid "Super + 1 to 0"
225−msgstr "Super + 0 till 9"
215+msgstr "Super + 1 till 0"
226216
227217#: qml/Components/KeyboardShortcutsOverlay.qml:182
228218msgid "Same as clicking on a launcher icon."
229219msgstr "Samma som att klicka på en ikon i Programstartaren."
230220
231221#: qml/Components/KeyboardShortcutsOverlay.qml:190
232−#, fuzzy
233−#| msgid "Super + W"
234222msgid "Super + A"
235−msgstr "Super + W"
223+msgstr "Super + A"
236224
237225#: qml/Components/KeyboardShortcutsOverlay.qml:195
238−#, fuzzy
239−#| msgid "Open the launcher"
240226msgid "Opens the Application Drawer."
241−msgstr "Öppna programstartaren"
227+msgstr "Öppnar programstartaren."
242228
243229#: qml/Components/KeyboardShortcutsOverlay.qml:235
244230msgid "Switching"
@@ -374,22 +360,20 @@ msgstr ""
374360"styrplatta för att interagera med pekaren."
375361
376362#: qml/Components/VirtualTouchPad.qml:327
377−#, fuzzy
378−#| msgid "Tap to return to call..."
379363msgid "Tap left button to click."
380−msgstr "Tryck för att återgå till samtal…"
364+msgstr "Tryck på vänster knapp för att klicka."
381365
382366#: qml/Components/VirtualTouchPad.qml:339
383367msgid "Tap right button to right click."
384−msgstr ""
368+msgstr "Tryck på höger knapp för att högerklicka."
385369
386370#: qml/Components/VirtualTouchPad.qml:351
387371msgid "Swipe with two fingers to scroll."
388−msgstr ""
372+msgstr "Svep med två fingrar för att scrolla."
389373
390374#: qml/Components/VirtualTouchPad.qml:396
391375msgid "Find more settings in the system settings."
392−msgstr ""
376+msgstr "Hitta fler inställningar i systeminställningarna."
393377
394378#: qml/Greeter/CoverPage.qml:127
395379msgid "Unlock"
@@ -419,16 +403,12 @@ msgid "Try again"
419403msgstr "Försök igen"
420404
421405#: qml/Greeter/Greeter.qml:551
422−#, fuzzy
423−#| msgid "Enter passphrase"
424406msgid "Enter passphrase to unlock"
425−msgstr "Ange lösenfras"
407+msgstr "Ange lösefras för att låsa upp"
426408
427409#: qml/Greeter/Greeter.qml:552
428−#, fuzzy
429−#| msgid "Enter passcode"
430410msgid "Enter passcode to unlock"
431−msgstr "Ange lösenkod"
411+msgstr "Ange lösenkod för att låsa upp"
432412
433413#: qml/Greeter/NarrowView.qml:235
434414msgid "Emergency"
@@ -447,10 +427,8 @@ msgid "Select desktop environment"
447427msgstr "Välj skrivbordsmiljö"
448428
449429#: qml/Launcher/Drawer.qml:250
450−#, fuzzy
451−#| msgid "Search"
452430msgid "Search…"
453−msgstr "Sök"
431+msgstr "Sök…"
454432
455433#: qml/Launcher/PullToRefreshScopeStyle.qml:56
456434msgid "Pull to refresh…"
@@ -557,13 +535,11 @@ msgstr "Dra ett program från ett fönster till det andra med tre fingrar"
557535
558536#: qml/Stage/Stage.qml:671
559537msgid "No running apps"
560−msgstr ""
538+msgstr "Inga appar körs"
561539
562540#: qml/Tutorial/TutorialLeftLong.qml:49
563−#, fuzzy
564−#| msgid "Long swipe from the left edge to open the Today scope"
565541msgid "Long swipe from the left edge to open the Application Drawer"
566−msgstr "Gör ett lång svep från vänstra kanten för öppna vyn Idag"
542+msgstr "Gör ett långt svep från vänstra kanten för öppna applikationsstartaren"
567543
568544#: qml/Tutorial/TutorialLeft.qml:47
569545msgid "Short swipe from the left edge to open the launcher"
@@ -699,12 +675,9 @@ msgstr ""
699675"kommer enheten att startas om automatiskt."
700676
701677#: qml/Wizard/Pages/79-system-update.qml:76
702−#, fuzzy
703−#| msgctxt "string identifying name of the update"
704−#| msgid "Ubuntu system"
705678msgctxt "string identifying name of the update"
706679msgid "Ubuntu Touch system"
707−msgstr "Ubuntusystem"
680+msgstr "Ubuntu Touch system"
708681
709682#: qml/Wizard/Pages/79-system-update.qml:83
710683#, qt-format
po/th.po+20−20
@@ -8,7 +8,7 @@ msgstr ""
88"Project-Id-Version: unity8\n"
99"Report-Msgid-Bugs-To: \n"
1010"POT-Creation-Date: 2021-01-29 11:42-0600\n"
11−"PO-Revision-Date: 2021-05-23 05:10+0000\n"
11+"PO-Revision-Date: 2021-10-14 16:02+0000\n"
1212"Last-Translator: Wannaphong Phatthiyaphaibun <wannaphong@yahoo.com>\n"
1313"Language-Team: Thai <https://translate.ubports.com/projects/ubports/lomiri/"
1414"th/>\n"
@@ -22,31 +22,31 @@ msgstr ""
2222
2323#: plugins/LightDM/Greeter.cpp:199
2424msgid "Password: "
25−msgstr ""
25+msgstr "รหัสผ่าน: "
2626
2727#: plugins/LightDM/Greeter.cpp:214
2828msgid "Username"
29−msgstr ""
29+msgstr "ชื่อผู้ใช้"
3030
3131#: plugins/LightDM/Greeter.cpp:258
3232msgid "Failed to authenticate"
33−msgstr ""
33+msgstr "ไม่สามารถตรวจสอบสิทธิ์ได้"
3434
3535#: plugins/LightDM/Greeter.cpp:260
3636msgid "Invalid password, please try again"
37−msgstr ""
37+msgstr "รหัสผ่านไม่ถูกต้อง กรุณาลองอีกครั้ง"
3838
3939#: plugins/LightDM/Greeter.cpp:270
4040msgid "Log In"
41−msgstr ""
41+msgstr "เข้าสู่ระบบ"
4242
4343#: plugins/LightDM/Greeter.cpp:270
4444msgid "Retry"
45−msgstr ""
45+msgstr "ลองอีกครั้ง"
4646
4747#: plugins/LightDM/UsersModel.cpp:157
4848msgid "Login"
49−msgstr ""
49+msgstr "เข้าสู่ระบบ"
5050
5151#: plugins/LightDM/UsersModel.cpp:165
5252msgid "Guest Session"
@@ -68,7 +68,7 @@ msgstr ""
6868#: qml/Components/Dialogs.qml:176
6969msgctxt "Title: Lock/Log out dialog"
7070msgid "Log out"
71−msgstr ""
71+msgstr "ลงชื่อออก"
7272
7373#: qml/Components/Dialogs.qml:177
7474msgid "Are you sure you want to log out?"
@@ -77,7 +77,7 @@ msgstr ""
7777#: qml/Components/Dialogs.qml:180
7878msgctxt "Button: Lock the system"
7979msgid "Lock"
80−msgstr ""
80+msgstr "ล็อก"
8181
8282#: qml/Components/Dialogs.qml:191
8383msgctxt "Button: Log out from the system"
@@ -101,16 +101,16 @@ msgstr ""
101101
102102#: qml/Components/Dialogs.qml:215
103103msgid "No"
104−msgstr ""
104+msgstr "ไม่"
105105
106106#: qml/Components/Dialogs.qml:223
107107msgid "Yes"
108−msgstr ""
108+msgstr "ใช่"
109109
110110#: qml/Components/Dialogs.qml:243
111111msgctxt "Title: Power off/Restart dialog"
112112msgid "Power"
113−msgstr ""
113+msgstr "พลังงาน"
114114
115115#: qml/Components/Dialogs.qml:244
116116msgid ""
@@ -121,7 +121,7 @@ msgstr ""
121121#: qml/Components/Dialogs.qml:248
122122msgctxt "Button: Power off the system"
123123msgid "Power off"
124−msgstr ""
124+msgstr "ปิดเครื่อง"
125125
126126#: qml/Components/Dialogs.qml:263
127127msgctxt "Button: Restart the system"
@@ -138,7 +138,7 @@ msgstr ""
138138
139139#: qml/Components/KeyboardShortcutsOverlay.qml:55
140140msgid "Lomiri"
141−msgstr ""
141+msgstr "Lomiri"
142142
143143#: qml/Components/KeyboardShortcutsOverlay.qml:62
144144msgid "PrtScr"
@@ -395,7 +395,7 @@ msgstr[1] ""
395395
396396#: qml/Greeter/Greeter.qml:550
397397msgid "Try again"
398−msgstr ""
398+msgstr "ลองอีกครั้ง"
399399
400400#: qml/Greeter/Greeter.qml:551
401401msgid "Enter passphrase to unlock"
@@ -407,7 +407,7 @@ msgstr ""
407407
408408#: qml/Greeter/NarrowView.qml:235
409409msgid "Emergency"
410−msgstr ""
410+msgstr "เหตุฉุกเฉิน"
411411
412412#: qml/Greeter/PromptList.qml:126
413413msgid "Passphrase"
@@ -423,7 +423,7 @@ msgstr ""
423423
424424#: qml/Launcher/Drawer.qml:250
425425msgid "Search…"
426−msgstr ""
426+msgstr "ค้นหา…"
427427
428428#: qml/Launcher/PullToRefreshScopeStyle.qml:56
429429msgid "Pull to refresh…"
@@ -435,7 +435,7 @@ msgstr ""
435435
436436#: qml/Notifications/NotificationMenuItemFactory.qml:124
437437msgid "Show password"
438−msgstr ""
438+msgstr "แสดงรหัสผ่าน"
439439
440440#: qml/Panel/ActiveCallHint.qml:79
441441msgid "Tap to return to call..."
@@ -699,7 +699,7 @@ msgstr ""
699699
700700#: qml/Wizard/Pages/80-finished.qml:126
701701msgid "Continue"
702−msgstr ""
702+msgstr "ดำเนินการต่อ"
703703
704704#: qml/Wizard/Pages/80-finished.qml:126
705705msgid "Get Started"
po/ur.po+31−28
@@ -8,99 +8,100 @@ msgstr ""
88"Project-Id-Version: unity8\n"
99"Report-Msgid-Bugs-To: \n"
1010"POT-Creation-Date: 2021-01-29 11:42-0600\n"
11−"PO-Revision-Date: 2015-05-05 03:12+0000\n"
12−"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13−"Language-Team: Urdu <ur@li.org>\n"
11+"PO-Revision-Date: 2022-02-11 14:14+0000\n"
12+"Last-Translator: Muhammad <alimuhammad42620@gmail.com>\n"
13+"Language-Team: Urdu <https://translate.ubports.com/projects/ubports/lomiri/"
14+"ur/>\n"
1415"Language: ur\n"
1516"MIME-Version: 1.0\n"
1617"Content-Type: text/plain; charset=UTF-8\n"
1718"Content-Transfer-Encoding: 8bit\n"
1819"Plural-Forms: nplurals=2; plural=n != 1;\n"
20+"X-Generator: Weblate 3.11.3\n"
1921"X-Launchpad-Export-Date: 2016-11-23 06:36+0000\n"
20−"X-Generator: Launchpad (build 18269)\n"
2122
2223#: plugins/LightDM/Greeter.cpp:199
2324msgid "Password: "
24−msgstr ""
25+msgstr "پاسورڈ "
2526
2627#: plugins/LightDM/Greeter.cpp:214
2728msgid "Username"
28−msgstr ""
29+msgstr "یوزر نیم"
2930
3031#: plugins/LightDM/Greeter.cpp:258
3132msgid "Failed to authenticate"
32−msgstr ""
33+msgstr "تصدیق میں ناکامی"
3334
3435#: plugins/LightDM/Greeter.cpp:260
3536msgid "Invalid password, please try again"
36−msgstr ""
37+msgstr "پاسورڈ غلط ھے، دوبارا کوشش کریں"
3738
3839#: plugins/LightDM/Greeter.cpp:270
3940msgid "Log In"
40−msgstr ""
41+msgstr "لاگ ان کریں"
4142
4243#: plugins/LightDM/Greeter.cpp:270
4344msgid "Retry"
44−msgstr ""
45+msgstr "دوبارا کوشش کریں"
4546
4647#: plugins/LightDM/UsersModel.cpp:157
4748msgid "Login"
48−msgstr ""
49+msgstr "لاگ ان کریں"
4950
5051#: plugins/LightDM/UsersModel.cpp:165
5152msgid "Guest Session"
52−msgstr ""
53+msgstr "مہمان سیشن"
5354
5455#: plugins/Unity/Launcher/launcheritem.cpp:48
5556#: plugins/Unity/Launcher/launcheritem.cpp:122
5657msgid "Pin shortcut"
57−msgstr ""
58+msgstr "شورت کٹ پن کیجیے"
5859
5960#: plugins/Unity/Launcher/launcheritem.cpp:54
6061msgid "Quit"
61−msgstr ""
62+msgstr "چھوڑیں"
6263
6364#: plugins/Unity/Launcher/launcheritem.cpp:122
6465msgid "Unpin shortcut"
65−msgstr ""
66+msgstr "شورت کٹ انپن کیجیے"
6667
6768#: qml/Components/Dialogs.qml:176
6869msgctxt "Title: Lock/Log out dialog"
6970msgid "Log out"
70−msgstr ""
71+msgstr "لاگ آؤٹ کیجیے"
7172
7273#: qml/Components/Dialogs.qml:177
7374msgid "Are you sure you want to log out?"
74−msgstr ""
75+msgstr "کیا آپ لاگ آؤٹ کرنا چاہتے ہیں؟"
7576
7677#: qml/Components/Dialogs.qml:180
7778msgctxt "Button: Lock the system"
7879msgid "Lock"
79−msgstr ""
80+msgstr "بند"
8081
8182#: qml/Components/Dialogs.qml:191
8283msgctxt "Button: Log out from the system"
8384msgid "Log Out"
84−msgstr ""
85+msgstr "لاگ آؤٹ"
8586
8687#: qml/Components/Dialogs.qml:199 qml/Components/Dialogs.qml:284
8788#: qml/Greeter/NarrowView.qml:215 qml/Wizard/Pages/passcode-confirm.qml:32
8889#: qml/Wizard/Pages/passcode-set.qml:32
8990msgid "Cancel"
90−msgstr ""
91+msgstr "نہیں"
9192
9293#: qml/Components/Dialogs.qml:211
9394msgctxt "Title: Reboot dialog"
9495msgid "Reboot"
95−msgstr ""
96+msgstr "ریبوٹ"
9697
9798#: qml/Components/Dialogs.qml:212
9899msgid "Are you sure you want to reboot?"
99−msgstr ""
100+msgstr "کیا آپ ریبوٹ کرنا چاہتے ہیں؟"
100101
101102#: qml/Components/Dialogs.qml:215
102103msgid "No"
103−msgstr ""
104+msgstr "نہیں"
104105
105106#: qml/Components/Dialogs.qml:223
106107msgid "Yes"
@@ -116,20 +117,22 @@ msgid ""
116117"Are you sure you would like\n"
117118"to power off?"
118119msgstr ""
120+"کیا آپ موبائل کو\n"
121+" بند کرنا چاہتے ہیں؟"
119122
120123#: qml/Components/Dialogs.qml:248
121124msgctxt "Button: Power off the system"
122125msgid "Power off"
123−msgstr ""
126+msgstr "بند"
124127
125128#: qml/Components/Dialogs.qml:263
126129msgctxt "Button: Restart the system"
127130msgid "Restart"
128−msgstr ""
131+msgstr "ریسارٹ"
129132
130133#: qml/Components/Dialogs.qml:276
131134msgid "Screenshot"
132−msgstr ""
135+msgstr "سکرین کی تصویر"
133136
134137#: qml/Components/KeyboardShortcutsOverlay.qml:41
135138msgid "Keyboard Shortcuts"
@@ -521,7 +524,7 @@ msgstr ""
521524
522525#: qml/Panel/PanelMenuPage.qml:92
523526msgid "Back"
524−msgstr ""
527+msgstr "پیچھے"
525528
526529#: qml/Stage/SideStage.qml:76
527530msgid "Drag using 3 fingers any application from one window to the other"
@@ -777,7 +780,7 @@ msgstr ""
777780
778781#: qml/Wizard/Pages/sim.qml:59
779782msgid "Restart"
780−msgstr ""
783+msgstr "دوباره شروع"
781784
782785#: qml/Wizard/Pages/sim.qml:78
783786msgid "You won’t be able to make calls or use text messaging without a SIM."
qml/Greeter/ButtonPrompt.qmlnew file+51
@@ -0,0 +1,51 @@
1+import QtQuick 2.4
2+import Ubuntu.Components 1.3
3+import "../Components"
4+
5+FocusScope {
6+ id: root
7+ objectName: "promptButton"
8+
9+ property alias text: buttonLabel.text
10+ property alias interactive: root.enabled
11+ property bool isSecret
12+ property bool loginError: false
13+ property bool hasKeyboard: false
14+ property string enteredText: ""
15+
16+ signal clicked()
17+ signal canceled()
18+ signal accepted(string response)
19+
20+ Keys.onSpacePressed: clicked();
21+ Keys.onReturnPressed: clicked();
22+ Keys.onEnterPressed: clicked();
23+
24+ anchors.fill: parent
25+
26+ activeFocusOnTab: true
27+
28+ Rectangle {
29+ anchors.fill: parent
30+ radius: units.gu(0.5)
31+ color: "#7A111111"
32+ Behavior on border.color {
33+ ColorAnimation{}
34+ }
35+ border {
36+ color: root.loginError ? theme.palette.normal.negative : theme.palette.normal.raisedSecondaryText
37+ width: root.loginError ? units.dp(2): units.dp(1)
38+ }
39+ }
40+
41+ MouseArea {
42+ anchors.fill: parent
43+ onClicked: parent.clicked();
44+ }
45+
46+ Label {
47+ id: buttonLabel
48+ anchors.centerIn: parent
49+ color: theme.palette.normal.raisedSecondaryText
50+ }
51+}
qml/Greeter/CoverPage.qml+27−8
@@ -16,6 +16,7 @@
1616 */
1717
1818import QtQuick 2.4
19+import QtGraphicalEffects 1.12
1920import Ubuntu.Components 1.3
2021import Ubuntu.Gestures 0.1
2122import "../Components"
@@ -28,11 +29,16 @@ Showable {
2829 property alias background: greeterBackground.source
2930 property alias backgroundSourceSize: greeterBackground.sourceSize
3031 property alias hasCustomBackground: backgroundShade.visible
32+ property alias backgroundShadeOpacity: backgroundShade.opacity
3133 property real panelHeight
3234 property var infographicModel
3335 property bool draggable: true
3436
35− property alias infographics: infographics
37+ property bool showInfographic: false
38+ property real infographicsLeftMargin: 0
39+ property real infographicsTopMargin: 0
40+ property real infographicsRightMargin: 0
41+ property real infographicsBottomMargin: 0
3642
3743 readonly property real showProgress: MathUtils.clamp((width - Math.abs(x + launcherOffset)) / width, 0, 1)
3844
@@ -99,18 +105,17 @@ Showable {
99105 objectName: "backgroundShade"
100106 anchors.fill: parent
101107 color: "black"
102− opacity: 0.4
103108 visible: false
104109 }
105110
106− Infographics {
107− id: infographics
108− objectName: "infographics"
109− model: root.infographicModel
110− clip: true // clip large data bubbles
111+ Item {
112+ id: infographicsArea
111113
112114 anchors {
113− topMargin: root.panelHeight
115+ leftMargin: root.infographicsLeftMargin
116+ topMargin: root.infographicsTopMargin ? root.infographicsTopMargin : root.panelHeight
117+ rightMargin: root.infographicsRightMargin
118+ bottomMargin: root.infographicsBottomMargin
114119 top: parent.top
115120 bottom: parent.bottom
116121 left: parent.left
@@ -118,6 +123,20 @@ Showable {
118123 }
119124 }
120125
126+ Loader {
127+ id: infographicsLoader
128+ objectName: "infographicsLoader"
129+ active: root.showInfographic && infographicsArea.width > units.gu(32)
130+ anchors.fill: infographicsArea
131+
132+ sourceComponent:Infographics {
133+ id: infographics
134+ objectName: "infographics"
135+ model: root.infographicModel
136+ clip: true // clip large data bubbles
137+ }
138+ }
139+
121140 Label {
122141 id: swipeHint
123142 objectName: "swipeHint"
qml/Greeter/Greeter.qml+37−2
@@ -58,6 +58,7 @@ Showable {
5858 readonly property bool locked: LightDMService.greeter.active && !LightDMService.greeter.authenticated && !forcedUnlock
5959
6060 property bool tabletMode
61+ property string usageMode
6162 property url viewSource // only used for testing
6263
6364 property int failedLoginsDelayAttempts: 7 // number of failed logins
@@ -66,6 +67,11 @@ Showable {
6667
6768 readonly property bool animating: loader.item ? loader.item.animating : false
6869
70+ property rect inputMethodRect
71+
72+ property bool hasKeyboard: false
73+ property int orientation
74+
6975 signal tease()
7076 signal sessionStarted()
7177 signal emergencyCall()
@@ -352,8 +358,7 @@ Showable {
352358 anchors.fill: parent
353359
354360 active: root.required
355− source: root.viewSource.toString() ? root.viewSource :
356− (d.multiUser || root.tabletMode) ? "WideView.qml" : "NarrowView.qml"
361+ source: root.viewSource.toString() ? root.viewSource : "GreeterView.qml"
357362
358363 onLoaded: {
359364 root.lockedApp = "";
@@ -460,6 +465,36 @@ Showable {
460465 property: "infographicModel"
461466 value: LightDMService.infographic
462467 }
468+
469+ Binding {
470+ target: loader.item
471+ property: "inputMethodRect"
472+ value: root.inputMethodRect
473+ }
474+
475+ Binding {
476+ target: loader.item
477+ property: "hasKeyboard"
478+ value: root.hasKeyboard
479+ }
480+
481+ Binding {
482+ target: loader.item
483+ property: "usageMode"
484+ value: root.usageMode
485+ }
486+
487+ Binding {
488+ target: loader.item
489+ property: "multiUser"
490+ value: d.multiUser
491+ }
492+
493+ Binding {
494+ target: loader.item
495+ property: "orientation"
496+ value: root.orientation
497+ }
463498 }
464499
465500 Connections {
qml/Greeter/GreeterPrompt.qml+64−187
@@ -25,226 +25,103 @@ FocusScope {
2525 focus: true
2626
2727 property bool isPrompt
28− property bool isAlphanumeric
28+ property bool isPinPrompt
2929 property string text
3030 property bool isSecret
3131 property bool interactive: true
32− readonly property alias enteredText: passwordInput.text
32+ property bool loginError: false
33+ readonly property string enteredText: loader.item.enteredText
34+ property bool hasKeyboard: false
35+ property bool waitingToAccept: false
3336
3437 signal clicked()
3538 signal canceled()
3639 signal accepted()
3740
38− function showFakePassword() {
39− // Just a silly hack for looking like 4 pin numbers got entered, if
40− // a fingerprint was used and we happen to be using a pin. This was
41− // a request from Design.
42− if (isSecret && isPrompt && !isAlphanumeric) {
43− passwordInput.text = "...."; // actual text doesn't matter
44− }
45− }
46−
4741 GSettings {
4842 id: unity8Settings
4943 schema.id: "com.canonical.Unity8"
5044 }
5145
52− StyledItem {
53− id: d
54−
55− readonly property color textColor: passwordInput.enabled ? theme.palette.normal.raisedText
56− : theme.palette.disabled.raisedText
57− readonly property color selectedColor: passwordInput.enabled ? theme.palette.normal.raised
58− : theme.palette.disabled.raised
59− readonly property color drawColor: passwordInput.enabled ? theme.palette.normal.raisedSecondaryText
60− : theme.palette.disabled.raisedSecondaryText
61− readonly property color errorColor: passwordInput.enabled ? theme.palette.normal.negative
62− : theme.palette.disabled.negative
63− }
46+ onEnteredTextChanged: if (waitingToAccept) root.accepted()
6447
65− Rectangle {
66− anchors.fill: parent
67− border.width: units.dp(1)
68− border.color: d.drawColor
69− radius: units.gu(0.5)
70− color: "transparent"
71− }
72−
73− Component.onCompleted: updateFocus()
74− onIsPromptChanged: updateFocus()
75− function updateFocus() {
76− if (root.isPrompt) {
77− passwordInput.focus = true;
78− } else {
79− promptButton.focus = true;
48+ function showFakePassword() {
49+ // Just a silly hack for looking like 4 pin numbers got entered, if
50+ // a fingerprint was used and we happen to be using a pin. This was
51+ // a request from Design.
52+ if (isSecret) {
53+ loader.item.enteredText = "...."; // actual text doesn't matter
8054 }
8155 }
8256
83− StyledItem {
84− id: promptButton
85− objectName: "promptButton"
86− anchors.fill: parent
87− visible: !root.isPrompt
88− activeFocusOnTab: true
57+ Loader {
58+ id: loader
59+ objectName: "promptLoader"
8960
90− styleName: "FocusShape"
61+ focus: true
9162
92− function triggered() {
93− if (root.interactive) {
94− root.clicked();
95− }
96− }
63+ anchors.fill: parent
9764
98− Rectangle {
99− height: parent.height;
100− width: parent.width
101− color: "transparent"
102− border {
103− color: d.textColor
104− width: units.dp(1)
65+ Connections {
66+ target: loader.item
67+ onClicked: root.clicked()
68+ onCanceled: root.canceled()
69+ onAccepted: {
70+ if (response == enteredText)
71+ root.accepted();
72+ else
73+ waitingToAccept = true;
10574 }
10675 }
10776
108− Keys.onSpacePressed: triggered();
109− Keys.onReturnPressed: triggered();
110− Keys.onEnterPressed: triggered();
111− MouseArea {
112− anchors.fill: parent
113− onClicked: parent.triggered();
77+ Binding {
78+ target: loader.item
79+ property: "text"
80+ value: root.text
11481 }
11582
116− Label {
117− anchors.centerIn: parent
118− color: d.textColor
119− text: root.text
83+ Binding {
84+ target: loader.item
85+ property: "isSecret"
86+ value: root.isSecret
12087 }
121− }
122−
123− TextField {
124− id: passwordInput
125− objectName: "promptField"
126− anchors.fill: parent
127− visible: root.isPrompt
128− opacity: fakeLabel.visible ? 0 : 1
129− activeFocusOnTab: true
13088
131− validator: RegExpValidator {
132− regExp: root.isAlphanumeric ? /^.*$/ : /^\d{4}$/
89+ Binding {
90+ target: loader.item
91+ property: "interactive"
92+ value: root.interactive
13393 }
13494
135− inputMethodHints: Qt.ImhSensitiveData | Qt.ImhNoPredictiveText |
136− Qt.ImhMultiLine | // so OSK doesn't close on Enter
137− (root.isAlphanumeric ? Qt.ImhNone : Qt.ImhDigitsOnly)
138− echoMode: root.isSecret ? TextInput.Password : TextInput.Normal
139− hasClearButton: false
140−
141− readonly property real frameSpacing: units.gu(0.5)
142−
143− style: StyledItem {
144− anchors.fill: parent
145− styleName: "FocusShape"
146−
147− // Properties needed by TextField
148− readonly property color color: d.textColor
149− readonly property color selectedTextColor: d.selectedColor
150− readonly property color selectionColor: d.textColor
151− readonly property color borderColor: "transparent"
152− readonly property color backgroundColor: "transparent"
153− readonly property color errorColor: d.errorColor
154− readonly property real frameSpacing: styledItem.frameSpacing
155−
156− // Properties needed by FocusShape
157− readonly property bool enabled: styledItem.enabled
158− readonly property bool keyNavigationFocus: styledItem.keyNavigationFocus
159− property bool activeFocusOnTab
95+ Binding {
96+ target: loader.item
97+ property: "loginError"
98+ value: root.loginError
16099 }
161100
162− secondaryItem: [
163− Row {
164− id: extraIcons
165− spacing: passwordInput.frameSpacing
166− Icon {
167− name: "keyboard-caps-enabled"
168− height: units.gu(3)
169− width: units.gu(3)
170− color: d.textColor
171− visible: root.isSecret && false // TODO: detect when caps lock is on
172− }
173− Icon {
174− name: "input-keyboard-symbolic"
175− height: units.gu(3)
176− width: units.gu(3)
177− color: d.textColor
178− visible: !unity8Settings.alwaysShowOsk
179− MouseArea {
180− anchors.fill: parent
181− onClicked: unity8Settings.alwaysShowOsk = true
182− }
183− }
184− }
185− ]
186−
187− onDisplayTextChanged: {
188− // We use onDisplayTextChanged instead of onTextChanged because
189− // displayText changes after text and if we did this before it
190− // updated, we would use the wrong displayText for fakeLabel.
191− if (!isAlphanumeric && text.length >= 4) {
192− // hard limit of 4 for passcodes right now
193− respond();
194− }
101+ Binding {
102+ target: loader.item
103+ property: "hasKeyboard"
104+ value: root.hasKeyboard
195105 }
196106
197− onAccepted: respond()
198−
199− function respond() {
200− if (root.interactive) {
201− root.accepted();
202− }
203− }
204−
205− Keys.onEscapePressed: {
206− root.canceled();
207− event.accepted = true;
208− }
209−
210− // We use our own custom placeholder label instead of the standard
211− // TextField one because the standard one hardcodes baseText as the
212− // palette color, whereas we want raisedSecondaryText.
213− Label {
214− id: hint
215− objectName: "promptHint"
216− anchors {
217− left: parent ? parent.left : undefined
218− right: parent ? parent.right : undefined
219− verticalCenter: parent ? parent.verticalCenter : undefined
220− leftMargin: units.gu(1.5)
221− rightMargin: anchors.leftMargin + extraIcons.width
222− }
223− text: root.text
224− visible: passwordInput.text == "" && !passwordInput.inputMethodComposing
225− enabled: visible
226− color: d.drawColor
227− elide: Text.ElideRight
228− }
107+ onLoaded: loader.item.focus = true
229108 }
230109
231− // Have a fake label that covers the text field after the user presses
232− // enter. What we *really* want is a disabled mode that doesn't lose OSK
233− // focus. Because our goal here is simply to keep the OSK up while
234− // we wait for PAM to get back to us, and while waiting, we don't want
235− // the user to be able to edit the field (simply because it would look
236− // weird if we allowed that). But until we have such a disabled mode,
237− // we'll fake it by covering the real text field with a label.
238− FadingLabel {
239− id: fakeLabel
240− anchors.verticalCenter: parent ? parent.verticalCenter : undefined
241− anchors.left: parent ? parent.left : undefined
242− anchors.right: parent ? parent.right : undefined
243− anchors.leftMargin: passwordInput.frameSpacing * 2
244− anchors.rightMargin: passwordInput.frameSpacing * 2 + extraIcons.width
245− color: d.drawColor
246− text: passwordInput.displayText
247− visible: root.isPrompt && !root.interactive
248− enabled: visible
249− }
110+ states: [
111+ State {
112+ name: "ButtonPrompt"
113+ when: !root.isPrompt
114+ PropertyChanges { target: loader; source: "ButtonPrompt.qml" }
115+ },
116+ State {
117+ name: "PinPrompt"
118+ when: root.isPinPrompt
119+ PropertyChanges { target: loader; source: "PinPrompt.qml" }
120+ },
121+ State {
122+ name: "TextPrompt"
123+ when: !root.isPinPrompt
124+ PropertyChanges { target: loader; source: "TextPrompt.qml" }
125+ }
126+ ]
250127}
qml/Greeter/GreeterView.qmlnew file+534
@@ -0,0 +1,534 @@
1+/*
2+ * Copyright (C) 2015-2016 Canonical, Ltd.
3+ * Copyright (C) 2021 UBports Foundation
4+ *
5+ * This program is free software; you can redistribute it and/or modify
6+ * it under the terms of the GNU General Public License as published by
7+ * the Free Software Foundation; version 3.
8+ *
9+ * This program is distributed in the hope that it will be useful,
10+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+ * GNU General Public License for more details.
13+ *
14+ * You should have received a copy of the GNU General Public License
15+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
16+ */
17+
18+import QtQuick 2.4
19+import QtQuick.Window 2.2
20+import QtGraphicalEffects 1.12
21+import Ubuntu.Components 1.3
22+import Ubuntu.Telephony 0.1 as Telephony
23+import "../Components"
24+
25+FocusScope {
26+ id: root
27+ objectName: "GreeterView"
28+
29+ focus: true
30+
31+ property url background
32+ property real backgroundSourceSize
33+ property real panelHeight
34+ property bool hasCustomBackground
35+ property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
36+ property var infographicModel
37+ property alias launcherOffset: coverPage.launcherOffset
38+ property alias currentIndex: loginList.currentIndex
39+ property alias delayMinutes: delayedLockscreen.delayMinutes // TODO
40+ property alias alphanumeric: loginList.alphanumeric
41+ property alias hasKeyboard: loginList.hasKeyboard
42+ property bool locked
43+ property bool waiting
44+ property var userModel // Set from outside
45+ property bool multiUser: false
46+ property int orientation
47+ property bool isLandscape: root.orientation == Qt.LandscapeOrientation ||
48+ root.orientation == Qt.InvertedLandscapeOrientation ||
49+ usageMode == "desktop"
50+ property bool isPortrait: (root.orientation == Qt.PortraitOrientation ||
51+ root.orientation == Qt.InvertedPortraitOrientation) &&
52+ usageMode != "desktop"
53+
54+ property string usageMode
55+
56+ readonly property bool animating: coverPage.showAnimation.running || coverPage.hideAnimation.running
57+ readonly property bool fullyShown: coverPage.showProgress === 1 || lockscreen.shown
58+ readonly property bool required: coverPage.required || lockscreen.required
59+ readonly property alias sessionToStart: loginList.currentSession
60+
61+ property rect inputMethodRect
62+
63+ signal selected(int index)
64+ signal responded(string response)
65+ signal tease()
66+ signal emergencyCall() // unused
67+
68+ function notifyAuthenticationFailed() {
69+ loginList.showError();
70+ }
71+
72+ function forceShow() {
73+ coverPage.show();
74+ }
75+
76+ function tryToUnlock(toTheRight) {
77+ var coverChanged = coverPage.shown;
78+ if (toTheRight) {
79+ coverPage.hideRight();
80+ } else {
81+ coverPage.hide();
82+ }
83+ if (root.locked) {
84+ lockscreen.show();
85+ loginList.tryToUnlock();
86+ return false;
87+ } else {
88+ root.responded("");
89+ return coverChanged;
90+ }
91+ }
92+
93+ function hide() {
94+ lockscreen.hide();
95+ coverPage.hide();
96+ }
97+
98+ function showFakePassword() {
99+ loginList.showFakePassword();
100+ }
101+
102+ function showErrorMessage(msg) {
103+ coverPage.showErrorMessage(msg);
104+ }
105+
106+ onLockedChanged: changeLockscreenState()
107+ onMultiUserChanged: changeLockscreenState()
108+
109+ function changeLockscreenState() {
110+ if (locked || multiUser) {
111+ lockscreen.maybeShow();
112+ } else {
113+ lockscreen.hide();
114+ }
115+ }
116+
117+ Keys.onSpacePressed: coverPage.hide();
118+ Keys.onReturnPressed: coverPage.hide();
119+ Keys.onEnterPressed: coverPage.hide();
120+
121+ Rectangle {
122+ anchors.fill: parent
123+ color: "black"
124+ opacity: lockscreen.showProgress * 0.8
125+ }
126+
127+ CoverPage {
128+ id: lockscreen
129+ objectName: "lockscreen"
130+ height: parent.height
131+ width: parent.width
132+ draggable: false
133+ state: "LoginList"
134+
135+ background: root.background
136+ backgroundSourceSize: root.backgroundSourceSize
137+ panelHeight: root.panelHeight
138+ hasCustomBackground: root.hasCustomBackground
139+ backgroundShadeOpacity: 0.6
140+
141+ showInfographic: isLandscape && root.usageMode != "phone" && (root.usageMode != "tablet" || root.multiUser) && !delayedLockscreen.visible
142+ infographicModel: root.infographicModel
143+
144+ shown: false
145+ opacity: 0
146+
147+ showAnimation: StandardAnimation { property: "opacity"; to: 1 }
148+ hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
149+
150+ infographicsTopMargin: parent.height * 0.125
151+ infographicsBottomMargin: parent.height * 0.125
152+ infographicsLeftMargin: loginList.x + loginList.width
153+
154+ onTease: root.tease()
155+
156+ onShowProgressChanged: {
157+ if (showProgress === 0 && !root.locked) {
158+ root.responded("");
159+ }
160+ }
161+
162+ LoginList {
163+ id: loginList
164+ objectName: "loginList"
165+
166+ width: units.gu(40)
167+ anchors {
168+ top: parent.top
169+ bottom: parent.bottom
170+ }
171+
172+ boxVerticalOffset: (height - highlightedHeight -
173+ inputMethodRect.height) / 2
174+ Behavior on boxVerticalOffset { UbuntuNumberAnimation {} }
175+
176+ enabled: !coverPage.shown && visible
177+ visible: !delayedLockscreen.visible
178+
179+ model: root.userModel
180+ onResponded: root.responded(response)
181+ onSelected: root.selected(index)
182+ onSessionChooserButtonClicked: parent.state = "SessionsList"
183+ onCurrentIndexChanged: setCurrentSession()
184+
185+ locked: root.locked
186+ waiting: root.waiting
187+
188+ Keys.forwardTo: [sessionChooserLoader.item]
189+
190+ Component.onCompleted: setCurrentSession()
191+
192+ function setCurrentSession() {
193+ currentSession = LightDMService.users.data(currentIndex, LightDMService.userRoles.SessionRole);
194+ }
195+ }
196+
197+ DelayedLockscreen {
198+ id: delayedLockscreen
199+ objectName: "delayedLockscreen"
200+ anchors.fill: parent
201+ visible: delayMinutes > 0
202+ alphaNumeric: loginList.alphanumeric
203+ }
204+
205+ function maybeShow() {
206+ if ((root.locked || root.multiUser) && !shown) {
207+ showNow();
208+ }
209+ }
210+
211+ Loader {
212+ id: sessionChooserLoader
213+
214+ height: loginList.height
215+ width: loginList.width
216+
217+ anchors {
218+ left: parent.left
219+ leftMargin: Math.min(parent.width * 0.16, units.gu(20))
220+ top: parent.top
221+ }
222+
223+ active: false
224+
225+ onLoaded: sessionChooserLoader.item.forceActiveFocus();
226+ onActiveChanged: {
227+ if (!active) return;
228+ item.updateHighlight(loginList.currentSession);
229+ }
230+
231+ Connections {
232+ target: sessionChooserLoader.item
233+ onSessionSelected: loginList.currentSession = sessionKey
234+ onShowLoginList: {
235+ lockscreen.state = "LoginList"
236+ loginList.tryToUnlock();
237+ }
238+ ignoreUnknownSignals: true
239+ }
240+ }
241+
242+ // Use an AbstractButton due to icon limitations with Button
243+ AbstractButton {
244+ id: sessionChooser
245+ objectName: "sessionChooserButton"
246+
247+ readonly property url icon: LightDMService.sessions.iconUrl(loginList.currentSession)
248+
249+ visible: LightDMService.sessions.count > 1 &&
250+ !LightDMService.users.data(loginList.currentUserIndex, LightDMService.userRoles.LoggedInRole)
251+
252+ height: units.gu(3.5)
253+ width: units.gu(3.5)
254+
255+ activeFocusOnTab: true
256+ anchors {
257+ right: parent.right
258+ rightMargin: units.gu(2)
259+
260+ bottom: parent.bottom
261+ bottomMargin: units.gu(1.5)
262+ }
263+
264+ Rectangle {
265+ id: badgeHighlight
266+
267+ anchors.fill: parent
268+ visible: parent.activeFocus
269+ color: "transparent"
270+ border.color: theme.palette.normal.focus
271+ border.width: units.dp(1)
272+ radius: 3
273+ }
274+
275+ Icon {
276+ id: badge
277+ anchors.fill: parent
278+ anchors.margins: units.dp(3)
279+ keyColor: "#ffffff" // icon providers give us white icons
280+ color: theme.palette.normal.raisedSecondaryText
281+ source: sessionChooser.icon
282+ }
283+
284+ Keys.onReturnPressed: {
285+ parent.state = "SessionsList";
286+ }
287+
288+ onClicked: {
289+ parent.state = "SessionsList";
290+ }
291+
292+ // Refresh the icon path if looking at different places at runtime
293+ // this is mainly for testing
294+ Connections {
295+ target: LightDMService.sessions
296+ onIconSearchDirectoriesChanged: {
297+ badge.source = LightDMService.sessions.iconUrl(root.currentSession)
298+ }
299+ }
300+ }
301+
302+ states: [
303+ State {
304+ name: "SessionsList"
305+ PropertyChanges { target: loginList; opacity: 0 }
306+ PropertyChanges { target: sessionChooserLoader;
307+ active: true;
308+ opacity: 1
309+ source: "SessionsList.qml"
310+ }
311+ },
312+
313+ State {
314+ name: "LoginList"
315+ PropertyChanges { target: loginList; opacity: 1 }
316+ PropertyChanges { target: sessionChooserLoader;
317+ active: false;
318+ opacity: 0
319+ source: "";
320+ }
321+ }
322+ ]
323+
324+ transitions: [
325+ Transition {
326+ from: "*"
327+ to: "*"
328+ UbuntuNumberAnimation {
329+ property: "opacity";
330+ }
331+ }
332+ ]
333+
334+ Component.onCompleted: if (root.multiUser) showNow()
335+ }
336+
337+ Rectangle {
338+ anchors.fill: parent
339+ color: "black"
340+ opacity: coverPage.showProgress * 0.8
341+ }
342+
343+ CoverPage {
344+ id: coverPage
345+ objectName: "coverPage"
346+ height: parent.height
347+ width: parent.width
348+ background: root.background
349+ hasCustomBackground: root.hasCustomBackground
350+ backgroundShadeOpacity: 0.4
351+ panelHeight: root.panelHeight
352+ draggable: !root.waiting
353+ onTease: root.tease()
354+ onClicked: hide()
355+ backgroundSourceSize: root.backgroundSourceSize
356+ infographicModel: root.infographicModel
357+
358+ showInfographic: !root.multiUser && root.usageMode != "desktop"
359+
360+ onShowProgressChanged: {
361+ if (showProgress === 0) {
362+ if (lockscreen.shown) {
363+ loginList.tryToUnlock();
364+ } else {
365+ root.responded("");
366+ }
367+ }
368+ }
369+
370+ Clock {
371+ id: clock
372+ anchors.centerIn: parent
373+ }
374+
375+ states: [
376+ State {
377+ name: "landscape-with-infographics"
378+ when: isLandscape && coverPage.showInfographic
379+ AnchorChanges {
380+ target: clock
381+ anchors.top: undefined
382+ anchors.horizontalCenter: undefined
383+ anchors.verticalCenter: undefined
384+ }
385+ PropertyChanges {
386+ target: clock;
387+ anchors.topMargin: undefined
388+ anchors.centerIn: coverPage
389+ anchors.horizontalCenterOffset: - coverPage.width / 2 + clock.width / 2 + units.gu(8)
390+ }
391+ PropertyChanges {
392+ target: coverPage
393+ infographicsLeftMargin: clock.width + units.gu(8)
394+ }
395+ },
396+ State {
397+ name: "portrait"
398+ when: isPortrait && coverPage.showInfographic
399+ AnchorChanges {
400+ target: clock;
401+ anchors.top: coverPage.top
402+ anchors.horizontalCenter: coverPage.horizontalCenter
403+ anchors.verticalCenter: undefined
404+ }
405+ PropertyChanges {
406+ target: clock;
407+ anchors.topMargin: units.gu(2) + panelHeight
408+ anchors.centerIn: undefined
409+ anchors.horizontalCenterOffset: 0
410+ }
411+ PropertyChanges {
412+ target: coverPage
413+ infographicsLeftMargin: 0
414+ }
415+ },
416+ State {
417+ name: "without-infographics"
418+ when: !coverPage.showInfographic
419+ AnchorChanges {
420+ target: clock
421+ anchors.top: undefined
422+ anchors.horizontalCenter: coverPage.horizontalCenter
423+ anchors.verticalCenter: coverPage.verticalCenter
424+ }
425+ PropertyChanges {
426+ target: clock;
427+ anchors.topMargin: 0
428+ anchors.centerIn: undefined
429+ anchors.horizontalCenterOffset: 0
430+ }
431+ PropertyChanges {
432+ target: coverPage
433+ infographicsLeftMargin: 0
434+ }
435+ }
436+ ]
437+ }
438+
439+ StyledItem {
440+ id: bottomBar
441+ visible: usageMode == "phone" && lockscreen.shown
442+ height: units.gu(4)
443+
444+ anchors.left: parent.left
445+ anchors.right: parent.right
446+ anchors.top: parent.bottom
447+ anchors.topMargin: - height * (1 - coverPage.showProgress)
448+ - ( inputMethodRect.height )
449+
450+ Label {
451+ text: i18n.tr("Cancel")
452+ anchors.left: parent.left
453+ anchors.leftMargin: units.gu(2)
454+ anchors.top: parent.top
455+ anchors.bottom: parent.bottom
456+ verticalAlignment: Text.AlignVCenter
457+ font.weight: Font.Light
458+ fontSize: "small"
459+ color: theme.palette.normal.raisedSecondaryText
460+
461+ AbstractButton {
462+ anchors.fill: parent
463+ anchors.leftMargin: -units.gu(2)
464+ anchors.rightMargin: -units.gu(2)
465+ onClicked: coverPage.show()
466+ }
467+ }
468+
469+ Label {
470+ objectName: "emergencyCallLabel"
471+ text: callManager.hasCalls ? i18n.tr("Return to Call") : i18n.tr("Emergency")
472+ anchors.right: parent.right
473+ anchors.rightMargin: units.gu(2)
474+ anchors.top: parent.top
475+ anchors.bottom: parent.bottom
476+ verticalAlignment: Text.AlignVCenter
477+ font.weight: Font.Light
478+ fontSize: "small"
479+ color: theme.palette.normal.raisedSecondaryText
480+ // TODO: uncomment once bug 1616538 is fixed
481+ // visible: telepathyHelper.ready && telepathyHelper.emergencyCallsAvailable
482+ enabled: visible
483+
484+ AbstractButton {
485+ anchors.fill: parent
486+ anchors.leftMargin: -units.gu(2)
487+ anchors.rightMargin: -units.gu(2)
488+ onClicked: root.emergencyCall()
489+ }
490+ }
491+ }
492+
493+ states: [
494+ State {
495+ name: "phone"
496+ when: root.usageMode == "phone" || (root.usageMode == "tablet" && isPortrait)
497+ AnchorChanges {
498+ target: loginList;
499+ anchors.horizontalCenter: lockscreen.horizontalCenter;
500+ anchors.left: undefined;
501+ }
502+ PropertyChanges {
503+ target: loginList;
504+ anchors.leftMargin: 0;
505+ }
506+ },
507+ State {
508+ name: "tablet"
509+ when: root.usageMode == "tablet" && isLandscape
510+ AnchorChanges {
511+ target: loginList;
512+ anchors.horizontalCenter: undefined;
513+ anchors.left: lockscreen.left;
514+ }
515+ PropertyChanges {
516+ target: loginList;
517+ anchors.leftMargin: Math.min(lockscreen.width * 0.16, units.gu(8));
518+ }
519+ },
520+ State {
521+ name: "desktop"
522+ when: root.usageMode == "desktop"
523+ AnchorChanges {
524+ target: loginList;
525+ anchors.horizontalCenter: undefined;
526+ anchors.left: lockscreen.left;
527+ }
528+ PropertyChanges {
529+ target: loginList;
530+ anchors.leftMargin: Math.min(lockscreen.width * 0.16, units.gu(20));
531+ }
532+ }
533+ ]
534+}
qml/Greeter/LoginAreaContainer.qml+3−3
@@ -21,18 +21,18 @@ Item {
2121 BorderImage {
2222 anchors {
2323 fill: parent
24− topMargin: -units.gu(1)
24+ topMargin: -units.gu(3)
2525 leftMargin: -units.gu(1.5)
2626 rightMargin: -units.gu(1.5)
2727 bottomMargin: -units.gu(1.5)
2828 }
2929 source: "../graphics/dropshadow2gu.sci"
30− opacity: 0.35
30+ opacity: 0
3131 }
3232
3333 UbuntuShape {
3434 anchors.fill: parent
3535 aspect: UbuntuShape.Flat
36− backgroundColor: theme.palette.normal.raised
36+ backgroundColor: "transparent"
3737 }
3838}
qml/Greeter/LoginList.qml+71−98
@@ -26,6 +26,7 @@ StyledItem {
2626
2727 property alias model: userList.model
2828 property alias alphanumeric: promptList.alphanumeric
29+ property alias hasKeyboard: promptList.hasKeyboard
2930 property int currentIndex
3031 property bool locked
3132 property bool waiting
@@ -39,6 +40,8 @@ StyledItem {
3940 property string currentSession // Initially set by LightDM
4041 readonly property string currentUser: userList.currentItem.username
4142
43+ readonly property alias currentUserIndex: userList.currentIndex
44+
4245 signal responded(string response)
4346 signal selected(int index)
4447 signal sessionChooserButtonClicked()
@@ -48,6 +51,7 @@ StyledItem {
4851 }
4952
5053 function showError() {
54+ promptList.loginError = true;
5155 wrongPasswordAnimation.start();
5256 }
5357
@@ -79,6 +83,7 @@ StyledItem {
7983
8084 onCurrentIndexChanged: {
8185 userList.currentIndex = currentIndex;
86+ promptList.loginError = false;
8287 }
8388
8489 LoginAreaContainer {
@@ -93,18 +98,6 @@ StyledItem {
9398
9499 height: Math.max(units.gu(15), promptList.height + units.gu(8))
95100 Behavior on height { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
96−
97− Label {
98− // HACK: Work around https://github.com/ubports/unity8/issues/185
99− text: _realName ? _realName : LightDMService.greeter.authenticationUser
100− visible: userList.count == 1
101− anchors {
102− left: parent.left
103− top: parent.top
104− topMargin: units.gu(2)
105− leftMargin: units.gu(2)
106− }
107− }
108101 }
109102
110103 ListView {
@@ -115,26 +108,31 @@ StyledItem {
115108 anchors.leftMargin: units.gu(2)
116109 anchors.rightMargin: units.gu(2)
117110
118− preferredHighlightBegin: highlightItem.y
119− preferredHighlightEnd: highlightItem.y
111+ preferredHighlightBegin: highlightItem.y + units.gu(1.5)
112+ preferredHighlightEnd: highlightItem.y + units.gu(1.5)
120113 highlightRangeMode: ListView.StrictlyEnforceRange
121114 highlightMoveDuration: root.moveDuration
122115 interactive: count > 1
123116
124117 readonly property bool movingInternally: moveTimer.running || userList.moving
125118
119+ onMovingChanged: if (!moving) root.selected(currentIndex)
120+
126121 onCurrentIndexChanged: {
127122 moveTimer.start();
128123 }
129124
125+ onCountChanged: if (root.currentIndex >= count) root.selected(0)
126+
130127 delegate: Item {
131128 width: userList.width
132129 height: root.cellHeight
133130
134131 readonly property bool belowHighlight: (userList.currentIndex < 0 && index > 0) || (userList.currentIndex >= 0 && index > userList.currentIndex)
132+ readonly property bool aboveCurrent: (userList.currentIndex > 0 && index < 0) || (userList.currentIndex >= 0 && index < userList.currentIndex)
135133 readonly property int belowOffset: root.highlightedHeight - root.cellHeight
136134 readonly property string userSession: session
137− readonly property string username: name
135+ readonly property string username: name ? name : ""
138136
139137 opacity: {
140138 // The goal here is to make names less and less opaque as they
@@ -154,40 +152,74 @@ StyledItem {
154152 return 1 - Math.min(1, (Math.abs(highlightDist) + root.cellHeight) / ((root.numAboveBelow + 1) * root.cellHeight))
155153 }
156154
157− FadingLabel {
158− objectName: "username" + index
159− visible: userList.count != 1 // HACK Hide username label until someone sorts out the anchoring with the keyboard-dismiss animation, Work around https://github.com/ubports/unity8/issues/185
155+ Row {
156+ spacing: units.gu(1)
157+// visible: userList.count != 1 // HACK Hide username label until someone sorts out the anchoring with the keyboard-dismiss animation, Work around https://github.com/ubports/unity8/issues/185
160158
161159 anchors {
162− left: parent.left
163160 leftMargin: units.gu(2)
164− right: parent.right
165161 rightMargin: units.gu(2)
162+ horizontalCenter: parent.horizontalCenter
166163 bottom: parent.top
167164 // Add an offset to bottomMargin for any items below the highlight
168− bottomMargin: -(units.gu(4) + (parent.belowHighlight ? parent.belowOffset : 0))
165+ bottomMargin: -(units.gu(4) + (parent.belowHighlight ? parent.belowOffset : parent.aboveCurrent ? -units.gu(5) : 0))
169166 }
170− text: userList.currentIndex === index
171− && name === "*other"
172− && LightDMService.greeter.authenticationUser !== ""
173− ? LightDMService.greeter.authenticationUser : realName
174− color: userList.currentIndex !== index ? theme.palette.normal.raised
175− : theme.palette.normal.raisedText
176−
177− Component.onCompleted: _realName = realName
178−
179− Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
180167
181168 Rectangle {
182169 id: activeIndicator
183− anchors.horizontalCenter: parent.left
184− anchors.horizontalCenterOffset: -units.gu(1)
185170 anchors.verticalCenter: parent.verticalCenter
186− color: userList.currentIndex !== index ? theme.palette.normal.raised
187− : theme.palette.normal.focus
171+ color: theme.palette.normal.raised
188172 visible: userList.count > 1 && loggedIn
189− height: units.gu(0.5)
173+ height: visible ? units.gu(0.5) : 0
174+ width: height
175+ }
176+
177+ Icon {
178+ id: userIcon
179+ name: "account"
180+ height: userList.currentIndex === index ? units.gu(4.5) : units.gu(3)
190181 width: height
182+ color: theme.palette.normal.raisedSecondaryText
183+ anchors.verticalCenter: parent.verticalCenter
184+ }
185+
186+ Column {
187+ anchors.verticalCenter: parent.verticalCenter
188+ spacing: units.gu(0.25)
189+
190+ FadingLabel {
191+ objectName: "username" + index
192+
193+ text: userList.currentIndex === index
194+ && name === "*other"
195+ && LightDMService.greeter.authenticationUser !== ""
196+ ? LightDMService.greeter.authenticationUser : realName ? realName : ""
197+ color: userList.currentIndex !== index ? theme.palette.normal.raised
198+ : theme.palette.normal.raisedSecondaryText
199+ font.weight: userList.currentIndex === index ? Font.Normal : Font.Light
200+ font.pointSize: units.gu(2)
201+
202+ width: highlightItem.width
203+ && contentWidth > highlightItem.width - userIcon.width - units.gu(4)
204+ ? highlightItem.width - userIcon.width - units.gu(4)
205+ : contentWidth
206+
207+ Component.onCompleted: _realName = realName
208+
209+ Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
210+ }
211+
212+ Row {
213+ spacing: units.gu(1)
214+
215+ FadingLabel {
216+ text: root.alphanumeric ? "Login with password" : "Login with pin"
217+ color: theme.palette.normal.raisedSecondaryText
218+ visible: userList.currentIndex === index && false
219+ font.weight: Font.Light
220+ font.pointSize: units.gu(1.25)
221+ }
222+ }
191223 }
192224 }
193225
@@ -197,7 +229,7 @@ StyledItem {
197229 right: parent.right
198230 top: parent.top
199231 // Add an offset to topMargin for any items below the highlight
200− topMargin: parent.belowHighlight ? parent.belowOffset : 0
232+ topMargin: parent.belowHighlight ? parent.belowOffset : parent.aboveCurrent ? -units.gu(5) : 0
201233 }
202234 height: parent.height
203235 enabled: userList.currentIndex !== index && parent.opacity > 0
@@ -217,67 +249,6 @@ StyledItem {
217249 }
218250 }
219251
220− // Use an AbstractButton due to icon limitations with Button
221− AbstractButton {
222− id: sessionChooser
223− objectName: "sessionChooserButton"
224−
225− readonly property alias icon: badge.source
226−
227− visible: LightDMService.sessions.count > 1 &&
228− !LightDMService.users.data(userList.currentIndex, LightDMService.userRoles.LoggedInRole)
229−
230− height: units.gu(3.5)
231− width: units.gu(3.5)
232−
233− activeFocusOnTab: true
234− anchors {
235− right: highlightItem.right
236− rightMargin: units.gu(2)
237−
238− top: highlightItem.top
239− topMargin: units.gu(1.5)
240− }
241−
242− Rectangle {
243− id: badgeHighlight
244−
245− anchors.fill: parent
246− visible: parent.activeFocus
247− color: "transparent"
248− border.color: theme.palette.normal.focus
249− border.width: units.dp(1)
250− radius: 3
251− }
252−
253− Icon {
254− id: badge
255− anchors.fill: parent
256− anchors.margins: units.dp(3)
257− keyColor: "#ffffff" // icon providers give us white icons
258− color: theme.palette.normal.raisedSecondaryText
259− source: LightDMService.sessions.iconUrl(root.currentSession)
260− }
261−
262− Keys.onReturnPressed: {
263− sessionChooserButtonClicked();
264− event.accepted = true;
265− }
266−
267− onClicked: {
268− sessionChooserButtonClicked();
269− }
270−
271− // Refresh the icon path if looking at different places at runtime
272− // this is mainly for testing
273− Connections {
274− target: LightDMService.sessions
275− onIconSearchDirectoriesChanged: {
276− badge.source = LightDMService.sessions.iconUrl(root.currentSession)
277− }
278− }
279− }
280−
281252 PromptList {
282253 id: promptList
283254 objectName: "promptList"
@@ -288,6 +259,8 @@ StyledItem {
288259 }
289260 width: highlightItem.width - anchors.margins * 2
290261
262+ focus: true
263+
291264 onClicked: {
292265 interactive = false;
293266 if (root.locked) {
qml/Greeter/NarrowView.qmldeleted−274
@@ -1,274 +0,0 @@
1−/*
2− * Copyright (C) 2015-2016 Canonical, Ltd.
3− * Copyright (C) 2021 UBports Foundation
4− *
5− * This program is free software; you can redistribute it and/or modify
6− * it under the terms of the GNU General Public License as published by
7− * the Free Software Foundation; version 3.
8− *
9− * This program is distributed in the hope that it will be useful,
10− * but WITHOUT ANY WARRANTY; without even the implied warranty of
11− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12− * GNU General Public License for more details.
13− *
14− * You should have received a copy of the GNU General Public License
15− * along with this program. If not, see <http://www.gnu.org/licenses/>.
16− */
17−
18−import QtQuick 2.4
19−import QtQuick.Window 2.2
20−import Ubuntu.Components 1.3
21−import Ubuntu.Telephony 0.1 as Telephony
22−import "../Components"
23−
24−FocusScope {
25− id: root
26−
27− property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
28− property alias launcherOffset: coverPage.launcherOffset
29− property alias currentIndex: loginList.currentIndex
30− property alias delayMinutes: delayedLockscreen.delayMinutes
31− property real panelHeight
32− property url background
33− property real backgroundSourceSize
34− property bool hasCustomBackground
35− property bool locked
36− property alias alphanumeric: loginList.alphanumeric
37− property alias userModel: loginList.model
38− property alias infographicModel: coverPage.infographicModel
39− property string sessionToStart
40− property bool waiting
41− readonly property bool fullyShown: coverPage.showProgress === 1 || lockscreen.shown
42− readonly property bool required: coverPage.required || lockscreen.required
43− readonly property bool animating: coverPage.showAnimation.running || coverPage.hideAnimation.running
44−
45− // so that it can be replaced in tests with a mock object
46− property var inputMethod: Qt.inputMethod
47−
48− signal selected(int index)
49− signal responded(string response)
50− signal tease()
51− signal emergencyCall()
52−
53− function hide() {
54− lockscreen.hide();
55− coverPage.hide();
56− }
57−
58− function showFakePassword() {
59− loginList.showFakePassword();
60− }
61−
62− function notifyAuthenticationFailed() {
63− loginList.showError();
64− }
65−
66− function showErrorMessage(msg) {
67− coverPage.showErrorMessage(msg);
68− }
69−
70− function forceShow() {
71− coverPage.show();
72− }
73−
74− function tryToUnlock(toTheRight) {
75− var coverChanged = coverPage.shown;
76− lockscreen.maybeShow();
77− if (toTheRight) {
78− coverPage.hideRight();
79− } else {
80− coverPage.hide();
81− }
82− return coverChanged;
83− }
84−
85− onLockedChanged: {
86− if (locked) {
87− lockscreen.maybeShow();
88− } else {
89− lockscreen.hide();
90− }
91− }
92−
93− Showable {
94− id: lockscreen
95− objectName: "lockscreen"
96− anchors.fill: parent
97− shown: false
98− opacity: 0
99−
100− showAnimation: StandardAnimation { property: "opacity"; to: 1 }
101− hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
102−
103− Rectangle {
104− // In case background fails to load or doesn't cover the whole screen
105− id: backgroundBackup
106− anchors.fill: parent
107− color: "black"
108− }
109−
110− Wallpaper {
111− id: lockscreenBackground
112− objectName: "lockscreenBackground"
113− anchors {
114− fill: parent
115− }
116− source: root.background
117− sourceSize: root.backgroundSourceSize
118− }
119−
120− // Darken background to match CoverPage
121− Rectangle {
122− objectName: "lockscreenShade"
123− anchors.fill: parent
124− color: "black"
125− opacity: root.hasCustomBackground ? 0.4 : 0
126− }
127−
128− LoginList {
129− id: loginList
130− objectName: "loginList"
131−
132− anchors {
133− horizontalCenter: parent.horizontalCenter
134− top: parent.top
135− bottom: parent.bottom
136− }
137− width: units.gu(40)
138− boxVerticalOffset: units.gu(14)
139− enabled: !coverPage.shown && visible
140− visible: !delayedLockscreen.visible
141−
142− locked: root.locked
143−
144− onSelected: if (enabled) root.selected(index)
145− onResponded: root.responded(response)
146− }
147−
148− DelayedLockscreen {
149− id: delayedLockscreen
150− objectName: "delayedLockscreen"
151− anchors.fill: parent
152− visible: delayMinutes > 0
153− alphaNumeric: loginList.alphanumeric
154− }
155−
156− function maybeShow() {
157− if (root.locked && !shown) {
158− showNow();
159− }
160− }
161− }
162−
163− Rectangle {
164− anchors.fill: parent
165− color: "black"
166− opacity: coverPage.showProgress * 0.8
167− }
168−
169− CoverPage {
170− id: coverPage
171− objectName: "coverPage"
172− height: parent.height
173− width: parent.width
174− background: root.background
175− hasCustomBackground: root.hasCustomBackground
176− panelHeight: root.panelHeight
177− draggable: !root.waiting
178− onTease: root.tease()
179− onClicked: hide()
180− backgroundSourceSize: root.backgroundSourceSize
181−
182− onShowProgressChanged: {
183− if (showProgress === 0) {
184− if (lockscreen.shown) {
185− loginList.tryToUnlock();
186− } else {
187− root.responded("");
188− }
189− }
190− }
191−
192− Clock {
193− anchors {
194− top: parent.top
195− topMargin: units.gu(2) + panelHeight
196− horizontalCenter: parent.horizontalCenter
197− }
198− }
199− }
200−
201− StyledItem {
202− id: bottomBar
203− visible: lockscreen.shown
204− height: units.gu(4)
205−
206− anchors.left: parent.left
207− anchors.right: parent.right
208− anchors.top: parent.bottom
209− anchors.topMargin: - height * (1 - coverPage.showProgress)
210− - (inputMethod && inputMethod.visible ?
211− inputMethod.keyboardRectangle.height : 0)
212−
213− Rectangle {
214− color: UbuntuColors.porcelain // matches OSK background
215− anchors.fill: parent
216− }
217−
218− Label {
219− text: i18n.tr("Cancel")
220− anchors.left: parent.left
221− anchors.leftMargin: units.gu(2)
222− anchors.top: parent.top
223− anchors.bottom: parent.bottom
224− verticalAlignment: Text.AlignVCenter
225− font.weight: Font.Light
226− fontSize: "small"
227− color: UbuntuColors.slate
228−
229− AbstractButton {
230− anchors.fill: parent
231− anchors.leftMargin: -units.gu(2)
232− anchors.rightMargin: -units.gu(2)
233− onClicked: coverPage.show()
234− }
235− }
236−
237− Label {
238− objectName: "emergencyCallLabel"
239− text: callManager.hasCalls ? i18n.tr("Return to Call") : i18n.tr("Emergency")
240− anchors.right: parent.right
241− anchors.rightMargin: units.gu(2)
242− anchors.top: parent.top
243− anchors.bottom: parent.bottom
244− verticalAlignment: Text.AlignVCenter
245− font.weight: Font.Light
246− fontSize: "small"
247− color: UbuntuColors.slate
248− // TODO: uncomment once bug 1616538 is fixed
249− // visible: telepathyHelper.ready && telepathyHelper.emergencyCallsAvailable
250− enabled: visible
251−
252− AbstractButton {
253− anchors.fill: parent
254− anchors.leftMargin: -units.gu(2)
255− anchors.rightMargin: -units.gu(2)
256− onClicked: root.emergencyCall()
257− }
258− }
259− }
260−
261− // FIXME: It's difficult to keep something tied closely to the OSK (bug
262− // 1616163). But as a hack to avoid the background peeking out,
263− // we add an extra Rectangle that just serves to hide the background
264− // during OSK animations.
265− Rectangle {
266− visible: bottomBar.visible
267− height: inputMethod && inputMethod.visible ?
268− inputMethod.keyboardRectangle.height : 0
269− anchors.bottom: parent.bottom
270− anchors.left: parent.left
271− anchors.right: parent.right
272− color: UbuntuColors.porcelain
273− }
274−}
qml/Greeter/PinPrompt.qmlnew file+184
@@ -0,0 +1,184 @@
1+import QtQuick 2.4
2+import Ubuntu.Components 1.3
3+import "../Components"
4+
5+FocusScope {
6+ id: root
7+
8+ property string text
9+ property bool isSecret
10+ property bool interactive: true
11+ property bool loginError: false
12+ property bool hasKeyboard: false
13+ property alias enteredText: passwordInput.text
14+
15+ signal clicked()
16+ signal canceled()
17+ signal accepted(string response)
18+
19+ StyledItem {
20+ id: d
21+
22+ readonly property color textColor: passwordInput.enabled ? theme.palette.normal.raisedText
23+ : theme.palette.disabled.raisedText
24+ readonly property color selectedColor: passwordInput.enabled ? theme.palette.normal.raised
25+ : theme.palette.disabled.raised
26+ readonly property color drawColor: passwordInput.enabled ? theme.palette.normal.raisedSecondaryText
27+ : theme.palette.disabled.raisedSecondaryText
28+ readonly property color errorColor: passwordInput.enabled ? theme.palette.normal.negative
29+ : theme.palette.disabled.negative
30+ }
31+
32+ TextField {
33+ id: passwordInput
34+ objectName: "promptField"
35+ anchors.left: extraIcons.left
36+ focus: root.focus
37+
38+ opacity: fakeLabel.visible ? 0 : 1
39+ activeFocusOnTab: true
40+
41+ onSelectedTextChanged: passwordInput.deselect()
42+ onCursorPositionChanged: cursorPosition = length
43+
44+ validator: RegExpValidator {
45+ regExp: /^\d{4}$/
46+ }
47+
48+ inputMethodHints: Qt.ImhSensitiveData | Qt.ImhNoPredictiveText |
49+ Qt.ImhMultiLine | // so OSK doesn't close on Enter
50+ Qt.ImhDigitsOnly
51+ echoMode: TextInput.Password
52+ hasClearButton: false
53+
54+ cursorDelegate: Item {}
55+
56+ passwordCharacter: "●"
57+ color: d.drawColor
58+
59+ readonly property real letterSpacing: units.gu(1.75)
60+ readonly property real frameSpacing: letterSpacing
61+
62+ font.pixelSize: units.gu(3)
63+ font.letterSpacing: letterSpacing
64+
65+ style: StyledItem {
66+ anchors.fill: parent
67+ styleName: "FocusShape"
68+
69+ // Properties needed by TextField
70+ readonly property color color: d.textColor
71+ readonly property color selectedTextColor: d.selectedColor
72+ readonly property color selectionColor: d.textColor
73+ readonly property color borderColor: "transparent"
74+ readonly property color backgroundColor: "transparent"
75+ readonly property color errorColor: d.errorColor
76+ readonly property real frameSpacing: 0
77+
78+ // Properties needed by FocusShape
79+ readonly property bool enabled: styledItem.enabled
80+ readonly property bool keyNavigationFocus: styledItem.keyNavigationFocus
81+ property bool activeFocusOnTab
82+ }
83+
84+ onDisplayTextChanged: {
85+ // We use onDisplayTextChanged instead of onTextChanged because
86+ // displayText changes after text and if we did this before it
87+ // updated, we would use the wrong displayText for fakeLabel.
88+ root.loginError = false;
89+ if (text.length >= 4) {
90+ // hard limit of 4 for passcodes right now
91+ respond();
92+ }
93+ }
94+
95+ onAccepted: respond()
96+
97+ function respond() {
98+ if (root.interactive) {
99+ root.accepted(passwordInput.text);
100+ }
101+ }
102+
103+ Keys.onEscapePressed: {
104+ root.canceled();
105+ event.accepted = true;
106+ }
107+ }
108+
109+ Row {
110+ id: extraIcons
111+ spacing: passwordInput.frameSpacing
112+ anchors {
113+ horizontalCenter: parent ? parent.horizontalCenter : undefined
114+ horizontalCenterOffset: passwordInput.letterSpacing / 2
115+ verticalCenter: passwordInput ? passwordInput.verticalCenter : undefined
116+ }
117+
118+ Label {
119+ id: pinHint
120+ objectName: "promptPinHint"
121+
122+ text: "○○○○"
123+ enabled: visible
124+ color: d.drawColor
125+ font {
126+ pixelSize: units.gu(3)
127+ letterSpacing: units.gu(1.75)
128+ }
129+ elide: Text.ElideRight
130+ }
131+ Icon {
132+ name: "keyboard-caps-enabled"
133+ height: units.gu(3)
134+ width: height
135+ color: d.drawColor
136+ visible: false // TODO: detect when caps lock is on
137+ anchors.verticalCenter: parent.verticalCenter
138+ }
139+ Icon {
140+ objectName: "greeterPromptKeyboardButton"
141+ name: "input-keyboard-symbolic"
142+ height: units.gu(3)
143+ width: height
144+ color: d.drawColor
145+ visible: !unity8Settings.alwaysShowOsk && root.hasKeyboard
146+ anchors.verticalCenter: parent.verticalCenter
147+ MouseArea {
148+ anchors.fill: parent
149+ onClicked: unity8Settings.alwaysShowOsk = true
150+ }
151+ }
152+ Icon {
153+ name: "dialog-warning-symbolic"
154+ height: units.gu(3)
155+ width: height
156+ color: d.drawColor
157+ visible: root.loginError
158+ anchors.verticalCenter: parent.verticalCenter
159+ }
160+ }
161+
162+ // Have a fake label that covers the text field after the user presses
163+ // enter. What we *really* want is a disabled mode that doesn't lose OSK
164+ // focus. Because our goal here is simply to keep the OSK up while
165+ // we wait for PAM to get back to us, and while waiting, we don't want
166+ // the user to be able to edit the field (simply because it would look
167+ // weird if we allowed that). But until we have such a disabled mode,
168+ // we'll fake it by covering the real text field with a label.
169+ Label {
170+ id: fakeLabel
171+ anchors.verticalCenter: extraIcons ? extraIcons.verticalCenter : undefined
172+ anchors.left: extraIcons ? extraIcons.left : undefined
173+ anchors.right: parent ? parent.right : undefined
174+ anchors.rightMargin: passwordInput.frameSpacing * 2 + extraIcons.width
175+ color: d.drawColor
176+ font {
177+ pixelSize: pinHint.font.pixelSize
178+ letterSpacing: pinHint.font.letterSpacing
179+ }
180+ text: passwordInput.displayText
181+ visible: !root.interactive
182+ enabled: visible
183+ }
184+}
qml/Greeter/PromptList.qml+21−18
@@ -25,6 +25,8 @@ FocusScope {
2525
2626 property bool alphanumeric: true
2727 property bool interactive: true
28+ property bool loginError: false
29+ property bool hasKeyboard: false
2830
2931 signal responded(string text)
3032 signal clicked()
@@ -65,10 +67,14 @@ FocusScope {
6567
6668 readonly property bool isLabel: model.type == LightDMService.prompts.Message ||
6769 model.type == LightDMService.prompts.Error
68− readonly property var modelData: model
70+ readonly property var modelText: model.text
71+ readonly property var modelType: model.type
72+ readonly property var modelIndex: model.index
6973
7074 sourceComponent: isLabel ? infoLabel : greeterPrompt
7175
76+ active: root.visible
77+
7278 onLoaded: {
7379 for (var i = 0; i < repeater.count; i++) {
7480 var item = repeater.itemAt(i);
@@ -79,12 +85,6 @@ FocusScope {
7985 }
8086 loader.item.opacity = 1;
8187 }
82−
83− Binding {
84− target: loader.item
85− property: "model"
86− value: loader.modelData
87− }
8888 }
8989 }
9090 }
@@ -93,17 +93,18 @@ FocusScope {
9393 id: infoLabel
9494
9595 FadingLabel {
96− objectName: "infoLabel" + model.index
96+ objectName: "infoLabel" + modelIndex
9797 width: root.width
9898
99− property var model
10099 readonly property bool isPrompt: false
101100
102− color: model.type === LightDMService.prompts.Message ? theme.palette.normal.raisedText
101+ color: modelType === LightDMService.prompts.Message ? theme.palette.normal.raisedSecondaryText
103102 : theme.palette.normal.negative
104103 fontSize: "small"
105104 textFormat: Text.PlainText
106− text: model.text
105+ text: modelText
106+
107+ visible: modelType === LightDMService.prompts.Message
107108
108109 Behavior on opacity { UbuntuNumberAnimation {} }
109110 opacity: 0
@@ -114,21 +115,23 @@ FocusScope {
114115 id: greeterPrompt
115116
116117 GreeterPrompt {
117− objectName: "greeterPrompt" + model.index
118+ objectName: "greeterPrompt" + modelIndex
118119 width: root.width
119120
120− property var model
121+ property bool isAlphanumeric: modelText !== "" || root.alphanumeric
121122
122123 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"))
124+ isPrompt: modelType !== LightDMService.prompts.Button
125+ isSecret: modelType === LightDMService.prompts.Secret
126+ isPinPrompt: isPrompt && !isAlphanumeric && isSecret
127+ loginError: root.loginError
128+ hasKeyboard: root.hasKeyboard
129+ text: modelText ? modelText : (isAlphanumeric ? i18n.tr("Passphrase") : i18n.tr("Passcode"))
127130
128131 onClicked: root.clicked()
129132 onAccepted: {
130133 // If there is another GreeterPrompt, focus it.
131− for (var i = model.index + 1; i < repeater.count; i++) {
134+ for (var i = modelIndex + 1; i < repeater.count; i++) {
132135 var item = repeater.itemAt(i).item;
133136 if (item.isPrompt) {
134137 item.forceActiveFocus();
qml/Greeter/SessionsList.qml+3−2
@@ -119,13 +119,14 @@ Item {
119119
120120 padding.leading: 0 // handled by parent's margins
121121
122− title.color: theme.palette.normal.raisedText
122+ title.color: theme.palette.normal.raisedSecondaryText
123123 title.font.pixelSize: units.gu(2.1)
124124 title.text: i18n.tr("Select desktop environment")
125125
126126 Icon {
127127 id: icon
128128 width: units.gu(3)
129+ color: theme.palette.normal.raisedSecondaryText
129130 SlotsLayout.position: SlotsLayout.Leading
130131 name: "go-previous"
131132
@@ -187,7 +188,7 @@ Item {
187188 ListItemLayout {
188189 id: layout
189190
190− readonly property color itemColor: theme.palette.normal.raisedText
191+ readonly property color itemColor: theme.palette.normal.raisedSecondaryText
191192 SessionIcon {
192193 id: sessionIcon
193194 source: icon_url
qml/Greeter/TextPrompt.qmlnew file+198
@@ -0,0 +1,198 @@
1+import QtQuick 2.4
2+import Ubuntu.Components 1.3
3+import "../Components"
4+
5+FocusScope {
6+ id: root
7+
8+ property string text
9+ property bool isSecret
10+ property bool interactive: true
11+ property bool loginError: false
12+ property bool hasKeyboard: false
13+ property alias enteredText: passwordInput.text
14+
15+ signal clicked()
16+ signal canceled()
17+ signal accepted(string response)
18+
19+ StyledItem {
20+ id: d
21+
22+ readonly property color textColor: passwordInput.enabled ? theme.palette.normal.raisedText
23+ : theme.palette.disabled.raisedText
24+ readonly property color selectedColor: passwordInput.enabled ? theme.palette.normal.raised
25+ : theme.palette.disabled.raised
26+ readonly property color drawColor: passwordInput.enabled ? theme.palette.normal.raisedSecondaryText
27+ : theme.palette.disabled.raisedSecondaryText
28+ readonly property color errorColor: passwordInput.enabled ? theme.palette.normal.negative
29+ : theme.palette.disabled.negative
30+ }
31+
32+ Rectangle {
33+ anchors.fill: parent
34+ radius: units.gu(0.5)
35+ color: "#7A111111"
36+ Behavior on border.color {
37+ ColorAnimation{}
38+ }
39+ border {
40+ color: root.loginError ? d.errorColor : d.drawColor
41+ width: root.loginError ? units.dp(2): units.dp(1)
42+ }
43+ }
44+
45+ TextField {
46+ id: passwordInput
47+ objectName: "promptField"
48+ anchors.fill: parent
49+ focus: root.focus
50+
51+ opacity: fakeLabel.visible ? 0 : 1
52+ activeFocusOnTab: true
53+
54+ onSelectedTextChanged: passwordInput.deselect()
55+
56+ validator: RegExpValidator {
57+ regExp: /^.*$/
58+ }
59+
60+ inputMethodHints: Qt.ImhSensitiveData | Qt.ImhNoPredictiveText |
61+ Qt.ImhMultiLine // so OSK doesn't close on Enter
62+ echoMode: root.isSecret ? TextInput.Password : TextInput.Normal
63+ hasClearButton: false
64+
65+ passwordCharacter: "●"
66+ color: d.drawColor
67+
68+ readonly property real frameSpacing: units.gu(1)
69+
70+ style: StyledItem {
71+ anchors.fill: parent
72+ styleName: "FocusShape"
73+
74+ // Properties needed by TextField
75+ readonly property color color: d.textColor
76+ readonly property color selectedTextColor: d.selectedColor
77+ readonly property color selectionColor: d.textColor
78+ readonly property color borderColor: "transparent"
79+ readonly property color backgroundColor: "transparent"
80+ readonly property color errorColor: d.errorColor
81+ readonly property real frameSpacing: styledItem.frameSpacing
82+
83+ // Properties needed by FocusShape
84+ readonly property bool enabled: styledItem.enabled
85+ readonly property bool keyNavigationFocus: styledItem.keyNavigationFocus
86+ property bool activeFocusOnTab
87+ }
88+
89+ secondaryItem: [
90+ Row {
91+ id: extraIcons
92+ spacing: passwordInput.frameSpacing
93+ anchors.verticalCenter: parent.verticalCenter
94+ Icon {
95+ name: "keyboard-caps-enabled"
96+ height: units.gu(3)
97+ width: units.gu(3)
98+ color: d.drawColor
99+ visible: root.isSecret && false // TODO: detect when caps lock is on
100+ anchors.verticalCenter: parent.verticalCenter
101+ }
102+ Icon {
103+ objectName: "greeterPromptKeyboardButton"
104+ name: "input-keyboard-symbolic"
105+ height: units.gu(3)
106+ width: units.gu(3)
107+ color: d.drawColor
108+ visible: !unity8Settings.alwaysShowOsk && root.hasKeyboard
109+ anchors.verticalCenter: parent.verticalCenter
110+ MouseArea {
111+ anchors.fill: parent
112+ onClicked: unity8Settings.alwaysShowOsk = true
113+ }
114+ }
115+ Icon {
116+ name: "dialog-warning-symbolic"
117+ height: units.gu(3)
118+ width: units.gu(3)
119+ color: d.drawColor
120+ visible: root.loginError
121+ anchors.verticalCenter: parent.verticalCenter
122+ }
123+ Icon {
124+ name: "toolkit_chevron-ltr_2gu"
125+ height: units.gu(2.5)
126+ width: units.gu(2.5)
127+ color: d.drawColor
128+ visible: !root.loginError
129+ anchors.verticalCenter: parent.verticalCenter
130+ MouseArea {
131+ anchors.fill: parent
132+ onClicked: root.accepted(passwordInput.text)
133+ }
134+ }
135+ }
136+ ]
137+
138+ onDisplayTextChanged: {
139+ // We use onDisplayTextChanged instead of onTextChanged because
140+ // displayText changes after text and if we did this before it
141+ // updated, we would use the wrong displayText for fakeLabel.
142+ root.loginError = false;
143+ }
144+
145+ onAccepted: respond()
146+
147+ function respond() {
148+ if (root.interactive) {
149+ root.accepted(passwordInput.text);
150+ }
151+ }
152+
153+ Keys.onEscapePressed: {
154+ root.canceled();
155+ event.accepted = true;
156+ }
157+ }
158+
159+ // We use our own custom placeholder label instead of the standard
160+ // TextField one because the standard one hardcodes baseText as the
161+ // palette color, whereas we want raisedSecondaryText.
162+ Label {
163+ id: passwordHint
164+ objectName: "promptHint"
165+ anchors {
166+ left: passwordInput ? passwordInput.left : undefined
167+ right: passwordInput ? passwordInput.right : undefined
168+ verticalCenter: passwordInput ? passwordInput.verticalCenter : undefined
169+ leftMargin: units.gu(2)
170+ rightMargin: anchors.leftMargin + extraIcons.width
171+ }
172+ text: root.text
173+ visible: passwordInput.text == "" && !passwordInput.inputMethodComposing
174+ enabled: visible
175+ color: d.drawColor
176+ elide: Text.ElideRight
177+ }
178+
179+ // Have a fake label that covers the text field after the user presses
180+ // enter. What we *really* want is a disabled mode that doesn't lose OSK
181+ // focus. Because our goal here is simply to keep the OSK up while
182+ // we wait for PAM to get back to us, and while waiting, we don't want
183+ // the user to be able to edit the field (simply because it would look
184+ // weird if we allowed that). But until we have such a disabled mode,
185+ // we'll fake it by covering the real text field with a label.
186+ FadingLabel {
187+ id: fakeLabel
188+ anchors.verticalCenter: parent ? parent.verticalCenter : undefined
189+ anchors.left: parent ? parent.left : undefined
190+ anchors.right: parent ? parent.right : undefined
191+ anchors.leftMargin: passwordInput.frameSpacing * 2
192+ anchors.rightMargin: passwordInput.frameSpacing * 2 + extraIcons.width
193+ color: d.drawColor
194+ text: passwordInput.displayText
195+ visible: !root.interactive
196+ enabled: visible
197+ }
198+}
qml/Greeter/WideView.qmldeleted−210
@@ -1,210 +0,0 @@
1−/*
2− * Copyright (C) 2015-2016 Canonical, Ltd.
3− * Copyright (C) 2021 UBports Foundation
4− *
5− * This program is free software; you can redistribute it and/or modify
6− * it under the terms of the GNU General Public License as published by
7− * the Free Software Foundation; version 3.
8− *
9− * This program is distributed in the hope that it will be useful,
10− * but WITHOUT ANY WARRANTY; without even the implied warranty of
11− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12− * GNU General Public License for more details.
13− *
14− * You should have received a copy of the GNU General Public License
15− * along with this program. If not, see <http://www.gnu.org/licenses/>.
16− */
17−
18−import QtQuick 2.4
19−import Ubuntu.Components 1.3
20−import "." 0.1
21−
22−FocusScope {
23− id: root
24− objectName: "WideView"
25−
26− focus: true
27−
28− property alias background: coverPage.background
29− property alias backgroundSourceSize: coverPage.backgroundSourceSize
30− property alias panelHeight: coverPage.panelHeight
31− property alias hasCustomBackground: coverPage.hasCustomBackground
32− property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
33− property alias infographicModel: coverPage.infographicModel
34− property alias launcherOffset: coverPage.launcherOffset
35− property alias currentIndex: loginList.currentIndex
36− property int delayMinutes // TODO
37− property alias alphanumeric: loginList.alphanumeric
38− property alias locked: loginList.locked
39− property alias waiting: loginList.waiting
40− property var userModel // Set from outside
41−
42− readonly property bool animating: coverPage.showAnimation.running || coverPage.hideAnimation.running
43− readonly property bool fullyShown: coverPage.showProgress === 1
44− readonly property bool required: coverPage.required
45− readonly property alias sessionToStart: loginList.currentSession
46−
47− // so that it can be replaced in tests with a mock object
48− property var inputMethod: Qt.inputMethod
49−
50− signal selected(int index)
51− signal responded(string response)
52− signal tease()
53− signal emergencyCall() // unused
54−
55− function notifyAuthenticationFailed() {
56− loginList.showError();
57− }
58−
59− function forceShow() {
60− // Nothing to do, we are always fully shown
61− }
62−
63− function tryToUnlock(toTheRight) {
64− if (root.locked) {
65− coverPage.show();
66− loginList.tryToUnlock();
67− return false;
68− } else {
69− var coverChanged = coverPage.shown;
70− if (toTheRight) {
71− coverPage.hideRight();
72− } else {
73− coverPage.hide();
74− }
75− return coverChanged;
76− }
77− }
78−
79− function hide() {
80− coverPage.hide();
81− }
82−
83− function showFakePassword() {
84− loginList.showFakePassword();
85− }
86−
87− Rectangle {
88− anchors.fill: parent
89− color: "black"
90− opacity: coverPage.showProgress * 0.8
91− }
92−
93− CoverPage {
94− id: coverPage
95− objectName: "coverPage"
96− height: parent.height
97− width: parent.width
98− draggable: !root.locked && !root.waiting
99− state: "LoginList"
100−
101− infographics {
102− anchors.topMargin: parent.height * 0.125
103− anchors.bottomMargin: parent.height * 0.125
104− anchors.leftMargin: loginList.x + loginList.width
105− }
106−
107− onTease: root.tease()
108−
109− onShowProgressChanged: {
110− if (showProgress === 0 && !root.locked) {
111− root.responded("");
112− }
113− }
114−
115− LoginList {
116− id: loginList
117− objectName: "loginList"
118−
119− width: units.gu(40)
120− anchors {
121− left: parent.left
122− leftMargin: Math.min(parent.width * 0.16, units.gu(20))
123− top: parent.top
124− bottom: parent.bottom
125− }
126−
127− boxVerticalOffset: (height - highlightedHeight -
128− (inputMethod && inputMethod.visible ?
129− inputMethod.keyboardRectangle.height : 0)) / 2
130− Behavior on boxVerticalOffset { UbuntuNumberAnimation {} }
131−
132− model: root.userModel
133− onResponded: root.responded(response)
134− onSelected: root.selected(index)
135− onSessionChooserButtonClicked: parent.state = "SessionsList"
136− onCurrentIndexChanged: setCurrentSession()
137−
138− Keys.forwardTo: [sessionChooserLoader.item]
139−
140− Component.onCompleted: setCurrentSession()
141−
142− function setCurrentSession() {
143− currentSession = LightDMService.users.data(currentIndex, LightDMService.userRoles.SessionRole);
144− }
145− }
146−
147− Loader {
148− id: sessionChooserLoader
149−
150− height: loginList.height
151− width: loginList.width
152−
153− anchors {
154− left: parent.left
155− leftMargin: Math.min(parent.width * 0.16, units.gu(20))
156− top: parent.top
157− }
158−
159− active: false
160−
161− onLoaded: sessionChooserLoader.item.forceActiveFocus();
162− onActiveChanged: {
163− if (!active) return;
164− item.updateHighlight(loginList.currentSession);
165− }
166−
167− Connections {
168− target: sessionChooserLoader.item
169− onSessionSelected: loginList.currentSession = sessionKey
170− onShowLoginList: {
171− coverPage.state = "LoginList"
172− loginList.tryToUnlock();
173− }
174− ignoreUnknownSignals: true
175− }
176− }
177−
178− states: [
179− State {
180− name: "SessionsList"
181− PropertyChanges { target: loginList; opacity: 0 }
182− PropertyChanges { target: sessionChooserLoader;
183− active: true;
184− opacity: 1
185− source: "SessionsList.qml"
186− }
187− },
188−
189− State {
190− name: "LoginList"
191− PropertyChanges { target: loginList; opacity: 1 }
192− PropertyChanges { target: sessionChooserLoader;
193− active: false;
194− opacity: 0
195− source: "";
196− }
197− }
198− ]
199−
200− transitions: [
201− Transition {
202− from: "*"
203− to: "*"
204− UbuntuNumberAnimation {
205− property: "opacity";
206− }
207− }
208− ]
209− }
210−}
qml/Launcher/BackgroundBlur.qmlnew file+81
@@ -0,0 +1,81 @@
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+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+import QtGraphicalEffects 1.0
20+
21+Item {
22+ id: root
23+
24+ property int blurAmount: 32
25+ property Item sourceItem
26+ property rect blurRect: Qt.rect(0,0,0,0)
27+ property alias cached: fastBlur.cached
28+
29+ Rectangle {
30+ id: blurMask
31+ color: "yellow"
32+ x: blurRect.x
33+ y: blurRect.y
34+ width: blurRect.width
35+ height: blurRect.height
36+ }
37+
38+ ShaderEffect {
39+ id: maskedBlurEffect
40+ x: blurRect.x
41+ y: blurRect.y
42+ width: blurRect.width
43+ height: blurRect.height
44+
45+ property variant source: ShaderEffectSource {
46+ id: shaderEffectSource
47+ sourceItem: root.sourceItem
48+ hideSource: false
49+ sourceRect: root.blurRect
50+ }
51+
52+ property var mask: ShaderEffectSource {
53+ sourceItem: blurMask
54+ hideSource: true
55+ }
56+
57+ fragmentShader: "
58+ varying highp vec2 qt_TexCoord0;
59+ uniform sampler2D source;
60+ uniform sampler2D mask;
61+ void main(void)
62+ {
63+ highp vec4 sourceColor = texture2D(source, qt_TexCoord0);
64+ highp vec4 maskColor = texture2D(mask, qt_TexCoord0);
65+
66+ sourceColor *= maskColor.a;
67+
68+ gl_FragColor = sourceColor;
69+ }"
70+ }
71+
72+ FastBlur {
73+ id: fastBlur
74+ x: blurRect.x
75+ y: blurRect.y
76+ width: blurRect.width
77+ height: blurRect.height
78+ source: maskedBlurEffect
79+ radius: Math.min(blurAmount, 128)
80+ }
81+ }
qml/Launcher/Drawer.qml+8−2
@@ -34,6 +34,7 @@ FocusScope {
3434 readonly property real delegateWidth: units.gu(10)
3535 property url background
3636 property alias backgroundSourceSize: background.sourceSize
37+ property bool staticBlurEnabled : true
3738 visible: x > -width
3839 property var fullyOpen: x === 0
3940 property var fullyClosed: x === -width
@@ -155,18 +156,21 @@ FocusScope {
155156
156157 Rectangle {
157158 anchors.fill: parent
158− color: "#111111"
159− opacity: 0.99
159+ color: "#BF000000"
160160
161161 Wallpaper {
162162 id: background
163163 objectName: "drawerBackground"
164+ visible: staticBlurEnabled
165+ enabled: staticBlurEnabled
164166 anchors.fill: parent
165167 source: root.background
166168 }
167169
168170 FastBlur {
169171 anchors.fill: background
172+ visible: staticBlurEnabled
173+ enabled: staticBlurEnabled
170174 source: background
171175 radius: 64
172176 cached: true
@@ -175,6 +179,8 @@ FocusScope {
175179 // Images with fastblur can't use opacity, so we'll put this on top
176180 Rectangle {
177181 anchors.fill: background
182+ visible: staticBlurEnabled
183+ enabled: staticBlurEnabled
178184 color: parent.color
179185 opacity: 0.67
180186 }
qml/Launcher/Launcher.qml+23−1
@@ -31,6 +31,8 @@ FocusScope {
3131 property bool lockedVisible: false
3232 property bool available: true // can be used to disable all interactions
3333 property alias inverted: panel.inverted
34+ property Item blurSource: null
35+ property bool interactiveBlur: false
3436 property int topPanelHeight: 0
3537 property bool drawerEnabled: true
3638 property alias privateMode: panel.privateMode
@@ -364,6 +366,20 @@ FocusScope {
364366 }
365367 }
366368
369+ BackgroundBlur {
370+ id: backgroundBlur
371+ anchors.fill: parent
372+ anchors.topMargin: root.inverted ? 0 : -root.topPanelHeight
373+ visible: root.interactiveBlur && root.blurSource && drawer.x > -drawer.width
374+ blurAmount: units.gu(6)
375+ sourceItem: root.blurSource
376+ blurRect: Qt.rect(panel.width,
377+ root.topPanelHeight,
378+ drawer.width + drawer.x - panel.width,
379+ height - root.topPanelHeight)
380+ cached: drawer.moving
381+ }
382+
367383 Drawer {
368384 id: drawer
369385 objectName: "drawer"
@@ -377,6 +393,7 @@ FocusScope {
377393 width: Math.min(root.width, units.gu(81))
378394 panelWidth: panel.width
379395 allowSlidingAnimation: !dragArea.dragging && !launcherDragArea.drag.active && panel.animate
396+ staticBlurEnabled: !root.interactiveBlur
380397
381398 onApplicationSelected: {
382399 root.launcherApplicationSelected(appId)
@@ -410,7 +427,7 @@ FocusScope {
410427 Connections {
411428 target: panel.dismissTimer
412429 onTriggered: {
413− if (root.autohideEnabled && !root.lockedVisible) {
430+ if (root.state !== "drawer" && root.autohideEnabled && !root.lockedVisible) {
414431 if (!edgeBarrier.containsMouse && !panel.preventHiding) {
415432 root.state = ""
416433 } else {
@@ -592,6 +609,11 @@ FocusScope {
592609 anchors.rightMargin: 0
593610 focus: false
594611 }
612+ PropertyChanges {
613+ target: root
614+ restoreEntryValues: false
615+ autohideEnabled: false
616+ }
595617 },
596618 State {
597619 name: "drawer"
qml/OrientedShell.qml+1
@@ -273,6 +273,7 @@ Item {
273273 nativeWidth: root.width
274274 nativeHeight: root.height
275275 mode: applicationArguments.mode
276+ interactiveBlur: applicationArguments.interactiveBlur
276277 hasMouse: pointerInputDevices > 0
277278 hasKeyboard: keyboardsModel.count > 0
278279 hasTouchscreen: touchScreensModel.count > 0
qml/Panel/Indicators/IndicatorItem.qml+6
@@ -117,6 +117,12 @@ IndicatorDelegate {
117117 source: modelData
118118 color: root.color
119119 Behavior on color { ColorAnimation { duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing } }
120+
121+ // Workaround indicators getting stretched/squished when (un)plugging external/virtual monitor
122+ onHeightChanged: {
123+ source = ""
124+ source = modelData
125+ }
120126 }
121127 }
122128 }
qml/Rotation/RotationStates.qml−3
@@ -139,9 +139,6 @@ StateGroup {
139139 } else if (Powerd.status === Powerd.Off) {
140140 // There's no point in animating if the user can't see it (display is off).
141141 d.animationType = d.noAnimation;
142− } else if (root.shell.showingGreeter) {
143− // A rotating greeter looks weird.
144− d.animationType = d.noAnimation;
145142 } else {
146143 if (!root.shell.mainApp) {
147144 // shouldn't happen but, anyway
qml/Shell.qml+10−3
@@ -1,4 +1,4 @@
1−/*
1+/*
22 * Copyright (C) 2013-2016 Canonical, Ltd.
33 * Copyright (C) 2019-2021 UBports Foundation
44 *
@@ -59,6 +59,7 @@ StyledItem {
5959 property alias panelAreaShowProgress: panel.panelAreaShowProgress
6060 property string usageScenario: "phone" // supported values: "phone", "tablet" or "desktop"
6161 property string mode: "full-greeter"
62+ property bool interactiveBlur: false
6263 property alias oskEnabled: inputMethod.enabled
6364 function updateFocusedAppOrientation() {
6465 stage.updateFocusedAppOrientation();
@@ -93,7 +94,7 @@ StyledItem {
9394
9495 readonly property bool orientationChangesEnabled: panel.indicators.fullyClosed
9596 && stage.orientationChangesEnabled
96− && (!greeter || !greeter.animating)
97+ && (!greeter.animating)
9798
9899 readonly property bool showingGreeter: greeter && greeter.shown
99100
@@ -104,7 +105,7 @@ StyledItem {
104105 if (startingUp) {
105106 // Ensure we don't rotate during start up
106107 return Qt.PrimaryOrientation;
107− } else if (showingGreeter || notifications.topmostIsFullscreen) {
108+ } else if (notifications.topmostIsFullscreen) {
108109 return Qt.PrimaryOrientation;
109110 } else {
110111 return shell.orientations ? shell.orientations.map(stage.supportedOrientations) : Qt.PrimaryOrientation;
@@ -436,10 +437,14 @@ StyledItem {
436437 enabled: panel.indicators.fullyClosed // hides OSK when panel is open
437438 hides: [launcher, panel.indicators, panel.applicationMenus]
438439 tabletMode: shell.usageScenario != "phone"
440+ usageMode: shell.usageScenario
441+ orientation: shell.orientation
439442 forcedUnlock: wizard.active || shell.mode === "full-shell"
440443 background: wallpaperResolver.background
441444 backgroundSourceSize: shell.largestScreenDimension
442445 hasCustomBackground: wallpaperResolver.hasCustomBackground
446+ inputMethodRect: inputMethod.visibleRect
447+ hasKeyboard: shell.hasKeyboard
443448 allowFingerprint: !dialogs.hasActiveDialog &&
444449 !notifications.topmostIsFullscreen &&
445450 !panel.indicators.shown
@@ -600,6 +605,8 @@ StyledItem {
600605 superTabPressed: physicalKeysMapper.superTabPressed
601606 panelWidth: units.gu(settings.launcherWidth)
602607 lockedVisible: (lockedByUser || shell.atDesktop) && lockAllowed
608+ blurSource: greeter.shown ? greeter : stages
609+ interactiveBlur: shell.interactiveBlur
603610 topPanelHeight: panel.panelHeight
604611 drawerEnabled: !greeter.active && tutorial.launcherLongSwipeEnabled
605612 privateMode: greeter.active
qml/Stage/ApplicationWindow.qml+12−3
@@ -53,6 +53,7 @@ FocusScope {
5353 if (surface) {
5454 surfaceContainer.surface = surface;
5555 d.liveSurface = surface.live;
56+ d.surfaceSeenLive = d.liveSurface;
5657 d.hadSurface = false;
5758 surfaceInitTimer.start();
5859 } else {
@@ -70,7 +71,12 @@ FocusScope {
7071 property bool liveSurface: false;
7172 property var con: Connections {
7273 target: root.surface
73− onLiveChanged: d.liveSurface = root.surface.live
74+ onLiveChanged: {
75+ d.liveSurface = root.surface.live;
76+ if (d.liveSurface) {
77+ d.surfaceSeenLive = true;
78+ }
79+ }
7480 }
7581 // using liveSurface instead of root.surface.live because with the latter
7682 // this expression is not reevaluated when root.surface changes
@@ -101,6 +107,7 @@ FocusScope {
101107 // might show the UI accommodating due to surface resizes on startup.
102108 // Remove this when possible
103109 property bool surfaceInitialized: false
110+ property bool surfaceSeenLive: false
104111
105112 readonly property bool supportsSurfaceResize:
106113 application &&
@@ -251,6 +258,7 @@ FocusScope {
251258 name: "splashScreen"
252259 when:
253260 !d.hadSurface && (!root.surface || !d.surfaceInitialized)
261+ && (d.liveSurface || !d.surfaceSeenLive)
254262 &&
255263 screenshotImage.status !== Image.Ready
256264 },
@@ -283,8 +291,9 @@ FocusScope {
283291 when:
284292 // The surface died while the application is running. It must have been closed
285293 // by the shell or the application decided to destroy it by itself
286− root.surface && d.surfaceInitialized && !d.liveSurface
287− && d.applicationState === ApplicationInfoInterface.Running
294+ root.surface && (d.surfaceInitialized || d.surfaceSeenLive) && !d.liveSurface
295+ && (d.applicationState === ApplicationInfoInterface.Running
296+ || d.applicationState === ApplicationInfoInterface.Starting)
288297 }
289298 ]
290299
src/ApplicationArguments.h+5
@@ -28,6 +28,7 @@ class ApplicationArguments : public QObject
2828 Q_OBJECT
2929 Q_PROPERTY(QString deviceName READ deviceName NOTIFY deviceNameChanged)
3030 Q_PROPERTY(QString mode READ mode CONSTANT)
31+ Q_PROPERTY(bool interactiveBlur READ interactiveBlur CONSTANT)
3132public:
3233 ApplicationArguments(QObject *parent = nullptr);
3334
@@ -42,12 +43,16 @@ public:
4243 void setMode(const QString &mode) { m_mode = mode; }
4344 QString mode() const { return m_mode; }
4445
46+ void setInteractiveBlur(const bool &value) { m_interactiveBlur = value; }
47+ bool interactiveBlur() { return m_interactiveBlur; }
48+
4549Q_SIGNALS:
4650 void deviceNameChanged(const QString&);
4751
4852private:
4953 QString m_deviceName;
5054 QString m_mode;
55+ bool m_interactiveBlur;
5156};
5257
5358#endif // APPLICATION_ARGUMENTS_H
src/ShellApplication.cpp+6
@@ -58,6 +58,12 @@ ShellApplication::ShellApplication(int & argc, char ** argv, bool isMirServer)
5858
5959 m_qmlArgs.setMode(parser.mode());
6060
61+ {
62+ char buffer[200];
63+ property_get("ubuntu.unity8.interactive_blur", buffer, "false");
64+ m_qmlArgs.setInteractiveBlur(QString(buffer) == QStringLiteral("true"));
65+ }
66+
6167 // The testability driver is only loaded by QApplication but not by QGuiApplication.
6268 // However, QApplication depends on QWidget which would add some unneeded overhead => Let's load the testability driver on our own.
6369 if (parser.hasTestability() || getenv("QT_LOAD_TESTABILITY")) {
tests/qmltests/CMakeLists.txt+1−2
@@ -29,8 +29,7 @@ add_unity8_qmltest(Components ZoomableImage)
2929add_unity8_qmltest(Greeter Clock)
3030add_unity8_qmltest(Greeter Greeter)
3131add_unity8_qmltest(Greeter Infographics)
32−add_unity8_qmltest(Greeter NarrowView)
33−add_unity8_qmltest(Greeter WideView)
32+add_unity8_qmltest(Greeter GreeterView)
3433add_unity8_qmltest_data(Greeter TestView)
3534add_unity8_qmltest(Launcher Launcher)
3635add_unity8_qmltest(Launcher Drawer)
tests/qmltests/Greeter/TestView.qml+7
@@ -38,6 +38,13 @@ Item {
3838 readonly property bool required: _required
3939 readonly property bool animating: _animating
4040
41+ property bool multiUser
42+ property int orientation
43+ property string usageMode
44+ property bool hasKeyboard
45+ property real backgroundSourceSize
46+ property rect inputMethodRect
47+
4148 property bool _fullyShown: true
4249 property bool _required: true
4350 property bool _animating: true
tests/qmltests/Greeter/tst_GreeterView.qmlnew file+1366
@@ -0,0 +1,1366 @@
1+/*
2+ * Copyright 2014-2016 Canonical Ltd.
3+ * Copyright 2021 UBports Foundation
4+ *
5+ * This program is free software; you can redistribute it and/or modify
6+ * it under the terms of the GNU General Public License as published by
7+ * the Free Software Foundation; version 3.
8+ *
9+ * This program is distributed in the hope that it will be useful,
10+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+ * GNU General Public License for more details.
13+ *
14+ * You should have received a copy of the GNU General Public License
15+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
16+ */
17+
18+import QtQuick 2.4
19+import QtQuick.Window 2.2
20+import QtTest 1.0
21+import ".."
22+import "../../../qml/Greeter"
23+import LightDMController 0.1
24+import LightDM.FullLightDM 0.1 as LightDM
25+import Ubuntu.Components 1.3
26+import Ubuntu.Components.ListItems 1.3 as ListItem
27+import Ubuntu.Telephony 0.1 as Telephony
28+import Unity.Test 0.1 as UT
29+
30+StyledItem {
31+ id: root
32+
33+ width: units.gu(160)
34+ height: units.gu(80)
35+ focus: true
36+
37+ theme.name: "Ubuntu.Components.Themes.Ambiance"
38+
39+ Component.onCompleted: {
40+ // must set the mock mode before loading the Shell
41+ LightDMController.userMode = "single";
42+ }
43+
44+ Row {
45+ anchors.fill: parent
46+ Rectangle {
47+ width: root.width - controls.width
48+ height: parent.height
49+ color: "grey"
50+
51+ Rectangle {
52+ id: viewContainer
53+ anchors.horizontalCenter: parent.horizontalCenter
54+ anchors.verticalCenter: parent.verticalCenter
55+ focus: true
56+
57+ Loader {
58+ id: loader
59+ width: parent.width
60+ height: parent.height
61+ focus: true
62+
63+ property bool itemDestroyed: false
64+ sourceComponent: Component {
65+ GreeterView {
66+ id: view
67+
68+ background: "/usr/share/backgrounds/warty-final-ubuntu.png"
69+ backgroundSourceSize: width
70+ userModel: LightDM.Users
71+ infographicModel: LightDM.Infographic
72+
73+ launcherOffset: parseFloat(launcherOffsetField.text)
74+ currentIndex: parseInt(currentIndexField.text, 10)
75+ delayMinutes: parseInt(delayMinutesField.text, 10)
76+ panelHeight: parseFloat(panelHeightField.text)
77+ locked: lockedCheckBox.checked
78+ inputMethodRect: fakeKeyboard.childrenRect
79+ property var testKeyboard: fakeKeyboard
80+
81+ usageMode: usageScenarioSelector.model[usageScenarioSelector.selectedIndex]
82+ multiUser: multiUserCheckBox.checked
83+ orientation: orientationSelector.model[orientationSelector.selectedIndex] == "landscape" ? Qt.LandscapeOrientation : Qt.PortraitOrientation
84+
85+ Component.onDestruction: {
86+ loader.itemDestroyed = true
87+ }
88+
89+ onSelected: {
90+ if (index >= 0)
91+ currentIndexField.text = index;
92+ }
93+
94+ Rectangle {
95+ id: fakeKeyboard
96+ color: "green"
97+ opacity: 0.7
98+ anchors.bottom: view.bottom
99+ width: view.width
100+ height: visible ? view.height * 0.6 : 0
101+ visible: keyboardVisibleCheckBox.checked
102+ Text {
103+ text: "Keyboard Rectangle"
104+ color: "yellow"
105+ font.bold: true
106+ fontSizeMode: Text.Fit
107+ minimumPixelSize: 10; font.pixelSize: 200
108+ verticalAlignment: Text.AlignVCenter
109+ x: (parent.width - width) / 2
110+ y: (parent.height - height) / 2
111+ width: parent.width
112+ height: parent.height
113+ }
114+ }
115+ }
116+ }
117+ }
118+
119+ state: loader.item ? loader.item.usageMode + "-" + (loader.item.isLandscape ? "landscape" : "portrait") : "phone-portrait"
120+
121+ states: [
122+ State {
123+ name: "phone-portrait"
124+ PropertyChanges {
125+ target: viewContainer
126+ width: units.gu(40)
127+ height: units.gu(71)
128+ }
129+ },
130+ State {
131+ name: "phone-landscape"
132+ PropertyChanges {
133+ target: viewContainer
134+ width: units.gu(71)
135+ height: units.gu(40)
136+ }
137+ },
138+ State {
139+ name: "tablet-landscape"
140+ PropertyChanges {
141+ target: viewContainer
142+ width: units.gu(100)
143+ height: units.gu(71)
144+ }
145+ },
146+ State {
147+ name: "tablet-portrait"
148+ PropertyChanges {
149+ target: viewContainer
150+ width: units.gu(71)
151+ height: units.gu(100)
152+ }
153+ },
154+ State {
155+ name: "desktop-portrait"
156+ PropertyChanges {
157+ target: viewContainer
158+ width: parent.width
159+ height: parent.height
160+ }
161+ },
162+ State {
163+ name: "desktop-landscape"
164+ PropertyChanges {
165+ target: viewContainer
166+ width: parent.width
167+ height: parent.height
168+ }
169+ }
170+ ]
171+ }
172+ }
173+
174+ Rectangle {
175+ id: controls
176+ color: theme.palette.normal.background
177+ width: units.gu(40)
178+ height: parent.height
179+ Column {
180+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
181+ spacing: units.gu(1)
182+ Row {
183+ Button {
184+ text: "Hide"
185+ onClicked: loader.item.hide()
186+ }
187+ }
188+ Row {
189+ Button {
190+ text: "Show Message"
191+ onClicked: LightDMService.prompts.append(messageField.text, LightDMService.prompts.Message)
192+ }
193+ TextField {
194+ id: messageField
195+ width: units.gu(10)
196+ text: ""
197+ }
198+ }
199+ Row {
200+ Button {
201+ text: "Show Prompt"
202+ onClicked: LightDMService.prompts.append(promptField.text, isSecretCheckBox.checked ? LightDMService.prompts.Secret : LightDMService.prompts.Question)
203+ }
204+ TextField {
205+ id: promptField
206+ width: units.gu(10)
207+ text: ""
208+ }
209+ CheckBox {
210+ id: isSecretCheckBox
211+ }
212+ Label {
213+ text: "secret"
214+ }
215+ }
216+ Row {
217+ Button {
218+ text: "Notify Auth Failure"
219+ onClicked: {
220+ loader.item.notifyAuthenticationFailed();
221+ }
222+ }
223+ }
224+ Row {
225+ Button {
226+ text: "Try To Unlock"
227+ onClicked: loader.item.tryToUnlock(toTheRightCheckBox.checked)
228+ }
229+ CheckBox {
230+ id: toTheRightCheckBox
231+ }
232+ Label {
233+ text: "toTheRight"
234+ }
235+ }
236+ Label {
237+ text: "Usage scenario"
238+ }
239+
240+ ListItem.ItemSelector {
241+ id: usageScenarioSelector
242+ anchors { left: parent.left; right: parent.right }
243+ activeFocusOnPress: false
244+ model: ["phone", "tablet", "desktop"]
245+ }
246+ Label {
247+ text: "Orientation"
248+ }
249+
250+ ListItem.ItemSelector {
251+ id: orientationSelector
252+ anchors { left: parent.left; right: parent.right }
253+ activeFocusOnPress: false
254+ model: ["portrait", "landscape"]
255+ }
256+ Row {
257+ TextField {
258+ id: launcherOffsetField
259+ width: units.gu(10)
260+ text: "0"
261+ }
262+ Label {
263+ text: "launcherOffset"
264+ }
265+ }
266+ Row {
267+ TextField {
268+ id: currentIndexField
269+ width: units.gu(10)
270+ text: "0"
271+ }
272+ Label {
273+ text: "currentIndex"
274+ }
275+ }
276+ Row {
277+ TextField {
278+ id: delayMinutesField
279+ width: units.gu(10)
280+ text: "0"
281+ }
282+ Label {
283+ text: "delayMinutes"
284+ }
285+ }
286+ Row {
287+ TextField {
288+ id: panelHeightField
289+ width: units.gu(10)
290+ text: "0"
291+ }
292+ Label {
293+ text: "panelHeight"
294+ }
295+ }
296+ Row {
297+ CheckBox {
298+ id: lockedCheckBox
299+ }
300+ Label {
301+ text: "locked"
302+ }
303+ }
304+ Row {
305+ CheckBox {
306+ id: multiUserCheckBox
307+ onClicked: {
308+ if (checked) {
309+ LightDMController.userMode = "full";
310+ } else {
311+ LightDMController.userMode = "single";
312+ }
313+ }
314+ Connections {
315+ target: LightDMController
316+ onSessionModeChanged: {
317+ if (LightDMController.userMode === "full") {
318+ multipleSessionsCheckbox.checked = true;
319+ } else {
320+ multipleSessionsCheckbox.checked = false;
321+ }
322+ }
323+ }
324+ }
325+ Label {
326+ text: "multi user"
327+ }
328+ }
329+ Row {
330+ CheckBox {
331+ id: alphanumericCheckBox
332+ checked: true
333+ }
334+ Label {
335+ text: "alphanumeric"
336+ }
337+ }
338+ Row {
339+ Label {
340+ text: "selected: " + selectedSpy.count
341+ }
342+ }
343+ Row {
344+ Label {
345+ text: "responded: " + respondedSpy.count
346+ }
347+ }
348+ Row {
349+ Label {
350+ text: "teased: " + teaseSpy.count
351+ }
352+ }
353+ Row {
354+ Label {
355+ text: "emergency: " + emergencySpy.count
356+ }
357+ }
358+ Row {
359+ Label {
360+ text: "infographics: " + infographicDataChangedSpy.count
361+ }
362+ }
363+ Row {
364+ id: multipleSessions
365+ CheckBox {
366+ id: multipleSessionsCheckbox
367+ onClicked: {
368+ if (checked) {
369+ LightDMController.sessionMode = "full";
370+ } else {
371+ LightDMController.sessionMode = "single";
372+ }
373+ }
374+ Connections {
375+ target: LightDMController
376+ onSessionModeChanged: {
377+ if (LightDMController.sessionMode === "full") {
378+ multipleSessionsCheckbox.checked = true;
379+ } else {
380+ multipleSessionsCheckbox.checked = false;
381+ }
382+ }
383+ }
384+ }
385+ Label {
386+ text: "Multiple Sessions"
387+ }
388+ }
389+ Row {
390+ Slider {
391+ id: numSessionsSlider
392+
393+ width: units.gu(10)
394+ minimumValue: 0
395+ maximumValue: LightDMController.numAvailableSessions
396+ value: LightDMController.numSessions
397+ visible: LightDMController.sessionMode === "full"
398+ Binding {
399+ target: LightDMController
400+ property: "numSessions"
401+ value: numSessionsSlider.value
402+ }
403+ }
404+ Label {
405+ text: "Available Sessions"
406+ }
407+ }
408+ Row {
409+ Button {
410+ text: "Reload View"
411+ onClicked: {
412+ loader.active = false;
413+ loader.active = true;
414+ }
415+ }
416+ }
417+ Row {
418+ CheckBox {
419+ id: keyboardVisibleCheckBox
420+ }
421+ Label {
422+ text: "Keyboard Visible"
423+ }
424+ }
425+ }
426+ }
427+ }
428+
429+/* FIXME: infographic user is hardcoded to "has-password",
430+ * needs to be set to current user to test in multiUser mode
431+ */
432+ Binding {
433+ target: LightDM.Infographic
434+ property: "username"
435+ value: "has-password"
436+ }
437+
438+ SignalSpy {
439+ id: selectedSpy
440+ target: loader.item
441+ signalName: "selected"
442+ }
443+
444+ SignalSpy {
445+ id: respondedSpy
446+ target: loader.item
447+ signalName: "responded"
448+ }
449+
450+ SignalSpy {
451+ id: teaseSpy
452+ target: loader.item
453+ signalName: "tease"
454+ }
455+
456+ SignalSpy {
457+ id: emergencySpy
458+ target: loader.item
459+ signalName: "emergencyCall"
460+ }
461+
462+ SignalSpy {
463+ id: infographicDataChangedSpy
464+ target: LightDM.Infographic
465+ signalName: "dataChanged"
466+ }
467+
468+ UT.UnityTestCase {
469+ name: "GreeterView"
470+ when: windowShown
471+
472+ property Item view: loader.status === Loader.Ready ? loader.item : null
473+ property url testIconDirectory: Qt.resolvedUrl("../../../qml/Greeter/graphics/session_icons")
474+
475+ function init() {
476+ selectIndex(0); // break binding with text field
477+ tryCompare(LightDMService.prompts, "count", 1);
478+ selectedSpy.clear();
479+ respondedSpy.clear();
480+ teaseSpy.clear();
481+ emergencySpy.clear();
482+ infographicDataChangedSpy.clear();
483+ LightDMController.reset();
484+ LightDM.Sessions.iconSearchDirectories = [testIconDirectory];
485+
486+ waitForRendering(view);
487+ var userList = findChild(view, "userList");
488+ tryCompare(userList, "movingInternally", false);
489+ }
490+
491+ function cleanup() {
492+ keyboardVisibleCheckBox.checked = false;
493+
494+ loader.itemDestroyed = false;
495+ loader.active = false;
496+ tryCompare(loader, "status", Loader.Null);
497+ tryCompare(loader, "item", null);
498+ tryCompare(loader, "itemDestroyed", true);
499+ loader.active = true;
500+ tryCompare(loader, "status", Loader.Ready);
501+ removeTimeConstraintsFromSwipeAreas(loader.item);
502+ LightDMController.sessionMode = "single";
503+ }
504+
505+ function setLightDMMockMode(mode) {
506+ LightDMController.userMode = mode;
507+ waitForRendering(root);
508+ }
509+
510+ function setUsageMode(usageMode) {
511+ view.usageMode = usageMode;
512+ if (usageMode == "desktop") {
513+ view.multiUser = true;
514+ setLightDMMockMode("full");
515+ view.orientation = Qt.LandscapeOrientation;
516+ } else if (usageMode == "phone") {
517+ view.multiUser = false;
518+ setLightDMMockMode("single");
519+ view.orientation = Qt.PortraitOrientation;
520+ telepathyHelper.ready = true;
521+ telepathyHelper.emergencyCallsAvailable = true;
522+ LightDM.Greeter.authenticate("no-password");
523+ tryCompare(LightDMService.prompts, "count", 1);
524+ }
525+ }
526+
527+ function swipeAwayCover(toTheRight) {
528+ if (toTheRight === undefined) {
529+ toTheRight = false;
530+ }
531+
532+ tryCompare(view, "fullyShown", true);
533+ var touchY = view.height / 2;
534+ if (toTheRight) {
535+ touchFlick(view, 0, touchY, view.width, touchY);
536+ } else {
537+ touchFlick(view, view.width, touchY, 0, touchY);
538+ }
539+ var coverPage = findChild(view, "coverPage");
540+ tryCompare(coverPage, "showProgress", 0);
541+ waitForRendering(view);
542+ }
543+
544+ function getIndexOf(name) {
545+ for (var i = 0; i < LightDM.Users.count; i++) {
546+ if (name === LightDM.Users.data(i, LightDM.UserRoles.NameRole)) {
547+ return i;
548+ }
549+ }
550+ fail("Didn't find name")
551+ return -1;
552+ }
553+
554+ function selectIndex(i) {
555+ var user = LightDM.Users.data(i, LightDM.UserRoles.NameRole);
556+ LightDM.Greeter.authenticate(user);
557+
558+ var userList = findChild(view, "userList");
559+ var promptList = findChild(view, "promptList");
560+
561+ view.currentIndex = i;
562+ tryCompare(promptList, "opacity", 1);
563+ tryCompare(userList, "movingInternally", false);
564+ }
565+
566+ function selectUser(name) {
567+ var i = getIndexOf(name);
568+ selectIndex(i);
569+ return i;
570+ }
571+
572+/* Desktop view tests (multiuser) */
573+ function test_sessionless_user_is_still_valid() {
574+ setUsageMode("desktop");
575+ var loginList = findChild(view, "loginList")
576+
577+ /*
578+ * If a user has never logged in before, or, for some reason
579+ * has no sessionHint, ensure that the model returns the default
580+ * session and that the view respects this
581+ */
582+ selectUser("no-session");
583+ compare(LightDM.Users.data(loginList.currentIndex, LightDM.UserRoles.SessionRole), LightDM.Greeter.defaultSession);
584+
585+ tryCompare(loginList, "currentSession", LightDM.Greeter.defaultSession);
586+ }
587+
588+ function test_changingSessionSticksToUser() {
589+ setUsageMode("desktop");
590+ var loginList = findChild(view, "loginList");
591+
592+ selectUser("invalid-session");
593+ tryCompare(loginList, "currentSession", "invalid");
594+
595+ selectUser("has-password");
596+ tryCompare(loginList, "currentSession", "ubuntu");
597+
598+ selectUser("invalid-session")
599+ tryCompare(loginList, "currentSession", "invalid");
600+ }
601+
602+ function test_sessionIconsAreValid() {
603+ setUsageMode("desktop");
604+ LightDMController.sessionMode = "full";
605+ selectUser("has-password");
606+
607+ // Test the login list icon is valid
608+ var sessionChooserButton = findChild(view, "sessionChooserButton");
609+ compare(sessionChooserButton.visible, true);
610+
611+ var loginList = findChild(view, "loginList");
612+ var session = String(loginList.currentSession).toLowerCase();
613+ var icon = String(sessionChooserButton.icon);
614+ compare(icon.indexOf(session) > -1, true);
615+
616+ // Test the session list icons are valid
617+ var lockscreen = findChild(view, "lockscreen");
618+ lockscreen.state = "SessionsList"
619+ var sessionsList = findChild(view, "sessionsList");
620+ tryCompare(sessionsList, "visible", true);
621+ }
622+
623+ function test_choosingNewSessionChangesLoginListIcon() {
624+ // Ensure the default session is selected (Ubuntu)
625+ cleanup();
626+ setUsageMode("desktop");
627+ swipeAwayCover();
628+
629+ LightDMController.sessionMode = "full";
630+ selectUser("has-password");
631+
632+ var sessionChooserButton = findChild(view, "sessionChooserButton");
633+ var icon = String(sessionChooserButton.icon);
634+ compare(icon.indexOf("ubuntu") > -1, true);
635+
636+ tap(sessionChooserButton)
637+ for(var i = 0; i < LightDM.Sessions.count; i++) {
638+ var delegateName = "sessionDelegate" + String(i);
639+ var currentDelegate = findChild(view, delegateName);
640+ verify(currentDelegate);
641+ var sessionKey = LightDM.Sessions.data(i,LightDM.SessionRoles.KeyRole);
642+ if (sessionKey === "gnome-classic") {
643+ waitForRendering(currentDelegate);
644+ tap(currentDelegate);
645+ waitForRendering(sessionChooserButton);
646+ break;
647+ }
648+ }
649+
650+ icon = String(sessionChooserButton.icon);
651+ compare(icon.indexOf("gnome") > -1, true,
652+ "Expected icon to contain gnome but it was " + icon);
653+ }
654+
655+ function test_noSessionsDoesntBreakView() {
656+ setUsageMode("desktop");
657+ LightDMController.sessionMode = "none";
658+ compare(LightDM.Sessions.count, 0)
659+ }
660+
661+ function test_sessionIconNotShownWithOneSession() {
662+ setUsageMode("desktop");
663+ LightDMController.sessionMode = "single";
664+ compare(LightDM.Sessions.count, 1);
665+
666+ var sessionChooserButton = findChild(view, "sessionChooserButton");
667+ tryCompare(sessionChooserButton, "visible", false);
668+ }
669+
670+ function test_sessionIconNotShownWithActiveUser() {
671+ setUsageMode("desktop");
672+ selectUser("active");
673+
674+ var sessionChooserButton = findChild(view, "sessionChooserButton");
675+ tryCompare(sessionChooserButton, "visible", false);
676+ }
677+
678+ function test_sessionIconShownWithMultipleSessions() {
679+ setUsageMode("desktop");
680+ LightDMController.sessionMode = "full";
681+ selectUser("has-password");
682+
683+ var sessionChooserButton = findChild(view, "sessionChooserButton");
684+ tryCompare(sessionChooserButton, "visible", true);
685+ }
686+
687+ function test_selected() {
688+ setUsageMode("desktop");
689+ swipeAwayCover();
690+ var delegate = findChild(view, "username2");
691+ tap(delegate);
692+ compare(selectedSpy.count, 1);
693+ compare(selectedSpy.signalArguments[0][0], 2);
694+ compare(view.currentIndex, 0); // confirm we didn't change
695+ }
696+
697+ function test_customBackground() {
698+ setUsageMode("desktop");
699+ var lockscreen = findChild(view, "lockscreen");
700+ var backgroundShade = findChild(lockscreen, "backgroundShade");
701+
702+ verify(!view.hasCustomBackground);
703+ verify(!backgroundShade.visible);
704+
705+ view.hasCustomBackground = true;
706+ verify(backgroundShade.visible);
707+ }
708+
709+ function test_respondedWithPassword() {
710+ setUsageMode("desktop");
711+ swipeAwayCover();
712+ selectUser("has-password");
713+ var greeterPrompt = findChild(view, "greeterPrompt0");
714+ verify(greeterPrompt.isSecret);
715+ typeString("password");
716+ keyClick(Qt.Key_Enter);
717+ compare(respondedSpy.count, 1);
718+ compare(respondedSpy.signalArguments[0][0], "password");
719+ }
720+
721+ function test_respondedWithNonSecret() {
722+ setUsageMode("desktop");
723+ swipeAwayCover();
724+ selectUser("question-prompt");
725+ var greeterPrompt = findChild(view, "greeterPrompt0");
726+ verify(!greeterPrompt.isSecret);
727+ typeString("foo");
728+ keyClick(Qt.Key_Enter);
729+ compare(respondedSpy.count, 1);
730+ compare(respondedSpy.signalArguments[0][0], "foo");
731+ }
732+
733+ function test_fullyShown_data() {
734+ return [
735+ {tag: "multiuser", multiUser: true, usageMode: "desktop", results: [true, true, true, true]},
736+ {tag: "singleuser", multiUser: false, usageMode: "desktop", results: [true, false, true, false]},
737+ {tag: "multiuser", multiUser: true, usageMode: "phone", results: [true, true, true, true]},
738+ {tag: "singleuser", multiUser: false, usageMode: "phone", results: [true, false, true, false]}
739+ ]
740+ }
741+
742+ function test_fullyShown(data) {
743+ setUsageMode(data.usageMode);
744+ view.multiUser = data.multiUser;
745+ tryCompare(view, "fullyShown", data.results[0]);
746+ tryCompare(view, "required", data.results[0]);
747+ swipeAwayCover();
748+ tryCompare(view, "fullyShown", data.results[1]);
749+ tryCompare(view, "required", data.results[1]);
750+ view.locked = true;
751+ tryCompare(view, "fullyShown", data.results[2]);
752+ tryCompare(view, "required", data.results[2]);
753+ view.locked = false;
754+ tryCompare(view, "fullyShown", data.results[3]);
755+ tryCompare(view, "required", data.results[3]);
756+ }
757+
758+ function test_infoPrompt() {
759+ setUsageMode("desktop");
760+ selectUser("info-prompt");
761+
762+ var infoLabel = findChild(view, "infoLabel0");
763+ compare(infoLabel.text, "Welcome to Unity Greeter");
764+ compare(infoLabel.textFormat, Text.PlainText);
765+
766+ verify(findChild(view, "greeterPrompt1") != null);
767+ }
768+
769+ function test_longInfoPrompt() {
770+ setUsageMode("desktop");
771+ selectUser("long-info-prompt");
772+
773+ var infoLabel = findChild(view, "infoLabel0");
774+ 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.");
775+ verify(infoLabel.contentWidth > infoLabel.width);
776+
777+ verify(findChild(view, "greeterPrompt1") != null);
778+ }
779+
780+ function test_multiInfoPrompt() {
781+ setUsageMode("desktop");
782+ selectUser("multi-info-prompt");
783+
784+ var infoLabel0 = findChild(view, "infoLabel0");
785+ compare(infoLabel0.text, "Welcome to Unity Greeter");
786+
787+ var infoLabel1 = findChild(view, "infoLabel1");
788+ compare(infoLabel1.text, "This is an error");
789+ compare(infoLabel1.color, theme.palette.normal.negative);
790+
791+ var infoLabel2 = findChild(view, "infoLabel2");
792+ compare(infoLabel2.text, "You should have seen three messages");
793+
794+ verify(findChild(view, "greeterPrompt3") != null);
795+ }
796+
797+ // Escape is used to reset the authentication, especially if PAM is unresponsive
798+ function test_escape() {
799+ setUsageMode("desktop");
800+ swipeAwayCover();
801+ var index = selectUser("has-password");
802+ selectedSpy.clear();
803+ view.locked = true;
804+ var promptField = findChild(view, "promptField");
805+ tap(promptField);
806+ verify(promptField.activeFocus);
807+ compare(promptField.opacity, 1);
808+
809+ typeString("password");
810+ keyClick(Qt.Key_Enter);
811+ verify(promptField.activeFocus);
812+ compare(promptField.opacity, 0); // hidden by fakeLabel
813+
814+ compare(selectedSpy.count, 0);
815+ keyClick(Qt.Key_Escape);
816+ compare(selectedSpy.count, 1);
817+ compare(selectedSpy.signalArguments[0][0], index);
818+
819+ selectIndex(index);
820+ var promptField = findChild(view, "promptField");
821+ verify(promptField.activeFocus);
822+ compare(promptField.opacity, 1);
823+ }
824+
825+ function test_unicode() {
826+ setUsageMode("desktop");
827+ var index = selectUser("unicode");
828+ var label = findChild(view, "username" + index);
829+ tryCompare(label, "text", "가나다라마");
830+ }
831+
832+ function test_authError() {
833+ setUsageMode("desktop");
834+ swipeAwayCover();
835+ var index = selectUser("auth-error");
836+ var greeterPrompt = findChild(view, "greeterPrompt1"); // after error message
837+ view.locked = true;
838+ compare(greeterPrompt.text, "Retry");
839+ tap(greeterPrompt);
840+ compare(respondedSpy.count, 0);
841+ compare(selectedSpy.count, 1);
842+ compare(selectedSpy.signalArguments[0][0], index);
843+ }
844+
845+ function test_noPassword() {
846+ setUsageMode("desktop");
847+ swipeAwayCover();
848+ selectUser("no-password");
849+ var greeterPrompt = findChild(view, "greeterPrompt0");
850+ compare(greeterPrompt.text, "Log In");
851+ tap(greeterPrompt);
852+ compare(selectedSpy.count, 0);
853+ compare(respondedSpy.count, 1);
854+ compare(respondedSpy.signalArguments[0][0], "");
855+ }
856+
857+ function test_loginListNotCoveredByKeyboard() {
858+ setUsageMode("desktop");
859+ var loginList = findChild(view, "loginList");
860+ compare(loginList.height, view.height);
861+
862+ // when the vkb shows up, loginList is moved up to remain fully uncovered
863+
864+ keyboardVisibleCheckBox.checked = true;
865+
866+ var halfway = (view.height - loginList.highlightedHeight) / 2;
867+ var halfwayWithOsk = halfway - view.inputMethodRect.height / 2;
868+ tryCompare(loginList, "boxVerticalOffset", halfwayWithOsk);
869+
870+ var highlightItem = findChild(loginList, "highlightItem");
871+ tryCompareFunction( function() {
872+ var highlightRect = highlightItem.mapToItem(view, 0, 0, highlightItem.width, highlightItem.height);
873+ return highlightRect.y + highlightRect.height <= view.testKeyboard.y;
874+ }, true);
875+
876+ // once the vkb goes away, loginList goes back to its full height
877+
878+ keyboardVisibleCheckBox.checked = false;
879+
880+ tryCompare(loginList, "boxVerticalOffset", halfway);
881+ }
882+
883+ function test_passphrase() {
884+ setUsageMode("desktop");
885+ swipeAwayCover();
886+ var index = selectUser("has-password");
887+ var promptField = findChild(view, "promptField");
888+
889+ verify(view.alphanumeric);
890+ compare(promptField.inputMethodHints & Qt.ImhDigitsOnly, 0);
891+
892+ keyClick(Qt.Key_D);
893+ compare(promptField.text, "d");
894+ }
895+
896+ function test_passcode() {
897+ setUsageMode("desktop");
898+ swipeAwayCover();
899+ var index = selectUser("has-pin");
900+ view.alphanumeric = false;
901+
902+ var promptField = findChild(view, "promptField");
903+ compare(promptField.inputMethodHints & Qt.ImhDigitsOnly, Qt.ImhDigitsOnly);
904+
905+ keyClick(Qt.Key_D);
906+ compare(promptField.text, "");
907+
908+ keyClick(Qt.Key_0);
909+ keyClick(Qt.Key_0);
910+ keyClick(Qt.Key_0);
911+ keyClick(Qt.Key_0);
912+ compare(promptField.text, "0000");
913+
914+ compare(respondedSpy.count, 1);
915+ compare(respondedSpy.signalArguments[0][0], "0000");
916+
917+ compare(promptField.opacity, 0);
918+ }
919+
920+ function test_loginListMovement_data() {
921+ return [
922+ {tag: "up", key: Qt.Key_Up, result: 1},
923+ {tag: "down", key: Qt.Key_Down, result: 3},
924+ ]
925+ }
926+
927+ function test_loginListMovement(data) {
928+ setUsageMode("desktop");
929+ swipeAwayCover();
930+ selectIndex(2);
931+ selectedSpy.clear();
932+
933+ keyClick(data.key);
934+ compare(selectedSpy.count, 1);
935+ compare(selectedSpy.signalArguments[0][0], data.result);
936+ }
937+
938+ function test_focusStaysActive() {
939+ setUsageMode("desktop");
940+ swipeAwayCover();
941+ selectUser("no-password");
942+ var greeterPrompt = findChild(view, "greeterPrompt0");
943+ verify(greeterPrompt.activeFocus);
944+ keyClick(Qt.Key_Enter);
945+ compare(selectedSpy.count, 0);
946+ compare(respondedSpy.count, 1);
947+ compare(respondedSpy.signalArguments[0][0], "");
948+ verify(greeterPrompt.activeFocus);
949+ keyClick(Qt.Key_Enter);
950+ compare(respondedSpy.count, 1);
951+
952+ selectUser("has-password");
953+ var greeterPrompt = findChild(view, "greeterPrompt0");
954+ verify(greeterPrompt.activeFocus);
955+ keyClick(Qt.Key_D);
956+ keyClick(Qt.Key_Enter);
957+ compare(selectedSpy.count, 0);
958+ compare(respondedSpy.count, 2);
959+ compare(respondedSpy.signalArguments[1][0], "d");
960+ verify(greeterPrompt.activeFocus);
961+ keyClick(Qt.Key_Enter);
962+ compare(respondedSpy.count, 2);
963+
964+ var index = selectUser("no-password");
965+ var greeterPrompt = findChild(view, "greeterPrompt0");
966+ view.locked = true;
967+ verify(greeterPrompt.activeFocus);
968+ keyClick(Qt.Key_Enter);
969+ compare(respondedSpy.count, 2);
970+ compare(selectedSpy.count, 1);
971+ compare(selectedSpy.signalArguments[0][0], index);
972+ verify(greeterPrompt.activeFocus);
973+ keyClick(Qt.Key_Enter);
974+ compare(selectedSpy.count, 1);
975+
976+ selectUser("no-password");
977+ var greeterPrompt = findChild(view, "greeterPrompt0");
978+ verify(greeterPrompt.activeFocus);
979+ }
980+
981+ function test_scrollChangesIndex() {
982+ /* Check if the index of the current user gets changed
983+ * if we scroll through the list.
984+ * Expected result: the list has moved at least by one
985+ * user and the index of the user is reported using the
986+ * selected signal in LoginList.qml
987+ * https://github.com/ubports/unity8/issues/397
988+ */
989+ setUsageMode("desktop");
990+ swipeAwayCover();
991+ var loginList = findChild(view, "loginList");
992+ // FIXME: Fix scrolling sensitivity (scrolling by -1 shouldn't move more than one user down)
993+ mouseWheel( loginList, loginList.width / 2, loginList.height / 2, 0, -1, null );
994+ selectedSpy.wait();
995+ tryVerify(function(){ return selectedSpy.signalArguments[0][0] > 0 });
996+ }
997+
998+// function test_dragChangesIndex() {
999+// /* Check if the index of the current user gets changed
1000+// * if we drag the list or swipe.on it.
1001+// * Expected result: the list has moved at least by one
1002+// * user and the index of the user is reported using the
1003+// * selected signal in LoginList.qml
1004+// * https://github.com/ubports/unity8/issues/397
1005+// */
1006+// setUsageMode("desktop");
1007+// swipeAwayCover();
1008+// var loginList = findChild(view, "loginList");
1009+// // FIXME: Fix scrolling sensitivity (the number of scrolled users is randomly changing)
1010+// touchFlick(loginList, loginList.width/2, loginList.height/3, loginList.width/2, loginList.height/3 -units.gu(2.1));
1011+// selectedSpy.wait();
1012+// tryVerify(function(){ return selectedSpy.signalArguments[0][0] > 0 });
1013+// }
1014+
1015+/* Mobile view tests */
1016+ function test_tease_data() {
1017+ return [
1018+ {tag: "left", x: 0, offset: 0, count: 1},
1019+ {tag: "leftWithOffsetPass", x: 10, offset: 10, count: 1},
1020+ {tag: "leftWithOffsetFail", x: 9, offset: 10, count: 0},
1021+ {tag: "right", x: view.width, offset: 0, count: 1},
1022+ ]
1023+ }
1024+ function test_tease(data) {
1025+ view.dragHandleLeftMargin = data.offset;
1026+ tap(view, data.x, 0);
1027+ tryCompare(teaseSpy, "count", data.count);
1028+ }
1029+
1030+ function test_respondedWithPin() {
1031+ LightDM.Greeter.authenticate("has-pin");
1032+ view.locked = true;
1033+ view.alphanumeric = false;
1034+ swipeAwayCover();
1035+ typeString("1234");
1036+ respondedSpy.wait();
1037+ compare(respondedSpy.signalArguments[0][0], "1234");
1038+ }
1039+
1040+ function test_respondedWithPassphrase() {
1041+ LightDM.Greeter.authenticate("has-password");
1042+ view.locked = true;
1043+ swipeAwayCover();
1044+ typeString("test");
1045+ keyClick(Qt.Key_Enter);
1046+ compare(respondedSpy.count, 1);
1047+ compare(respondedSpy.signalArguments[0][0], "test");
1048+ }
1049+
1050+ function test_respondedWithSwipe_data() {
1051+ return [
1052+ {tag: "left", toTheRight: false, hiddenX: -view.width},
1053+ {tag: "right", toTheRight: true, hiddenX: view.width},
1054+ ];
1055+ }
1056+ function test_respondedWithSwipe(data) {
1057+ swipeAwayCover(data.toTheRight);
1058+ var coverPage = findChild(view, "coverPage");
1059+ compare(coverPage.x, data.hiddenX);
1060+ compare(respondedSpy.count, 1);
1061+ compare(respondedSpy.signalArguments[0][0], "");
1062+ }
1063+
1064+ function test_emergencyCall_data() {
1065+ return [
1066+ {tag: "phone", available: true},
1067+ {tag: "desktop", available: false},
1068+ ];
1069+ }
1070+ function test_emergencyCall(data) {
1071+ telepathyHelper.emergencyCallsAvailable = data.available;
1072+ view.locked = true;
1073+ swipeAwayCover();
1074+ var emergencyCallLabel = findChild(view, "emergencyCallLabel");
1075+ tap(emergencyCallLabel);
1076+ if (!data.available) {
1077+ expectFail("", "Bug 1616538 prevents us supporting conditional emergency button support");
1078+ }
1079+ compare(emergencyCallLabel.visible, data.available ? true : false);
1080+ compare(emergencySpy.count, data.available ? 1 : 0);
1081+ }
1082+
1083+ function test_emergencyCallAvailability() {
1084+ view.locked = true;
1085+ var emergencyCallLabel = findChild(view, "emergencyCallLabel");
1086+ verify(emergencyCallLabel.visible);
1087+
1088+ telepathyHelper.emergencyCallsAvailable = false;
1089+ telepathyHelper.ready = true;
1090+ expectFail("", "Bug 1616538 prevents us supporting conditional emergency button support");
1091+ verify(!emergencyCallLabel.visible);
1092+
1093+ telepathyHelper.emergencyCallsAvailable = true;
1094+ telepathyHelper.ready = false;
1095+ verify(!emergencyCallLabel.visible);
1096+ }
1097+
1098+ function test_tryToUnlock() {
1099+ var coverPage = findChild(view, "coverPage");
1100+ tryCompare(coverPage, "showProgress", 1);
1101+ compare(view.tryToUnlock(false), true);
1102+ tryCompare(coverPage, "showProgress", 0);
1103+ compare(view.tryToUnlock(false), false);
1104+ }
1105+
1106+ /*
1107+ Regression test for https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1388359
1108+ "User metrics can no longer be changed by double tap"
1109+ */
1110+ function test_doubleTapSwitchesToNextInfographic() {
1111+ setUsageMode("phone");
1112+ infographicDataChangedSpy.clear();
1113+ var coverPage = findChild(view, "coverPage");
1114+ verify(coverPage);
1115+
1116+ var infographicPrivate = findInvisibleChild(coverPage, "infographicPrivate");
1117+ verify(infographicPrivate);
1118+
1119+ // wait for the UI to settle down before double tapping it
1120+ tryCompare(infographicPrivate, "animating", false);
1121+
1122+ var dataCircle = findChild(coverPage, "dataCircle");
1123+ verify(dataCircle);
1124+
1125+ mouseDoubleClickSequence(dataCircle);
1126+
1127+ tryCompare(infographicDataChangedSpy, "count", 1);
1128+ }
1129+
1130+ function test_movesBackIntoPlaceWhenNotDraggedFarEnough() {
1131+ var coverPage = findChild(view, "coverPage");
1132+
1133+ var dragEvaluator = findInvisibleChild(coverPage, "edgeDragEvaluator");
1134+ verify(dragEvaluator);
1135+
1136+ // Make it easier to get a rejection/rollback. Otherwise would have to inject
1137+ // a fake timer into dragEvaluator.
1138+ // Afterall, we are testing if the CoverPage indeed moves back on a
1139+ // rollback decision, not the drag evaluation itself.
1140+ dragEvaluator.minDragDistance = dragEvaluator.maxDragDistance / 2;
1141+
1142+ // it starts as fully shown
1143+ compare(coverPage.x, 0);
1144+
1145+ // then we drag it a bit
1146+ var startX = coverPage.width - 1;
1147+ var touchY = coverPage.height / 2;
1148+ var dragXDelta = -(dragEvaluator.minDragDistance * 0.3);
1149+ touchFlick(coverPage,
1150+ startX , touchY, // start pos
1151+ startX + dragXDelta, touchY, // end pos
1152+ true /* beginTouch */, false /* endTouch */);
1153+
1154+ // which should make it move a bit
1155+ tryCompareFunction(function() {return coverPage.x < 0;}, true);
1156+
1157+ // then we release it
1158+ touchRelease(coverPage, startX + dragXDelta, touchY);
1159+
1160+ // which should make it move back into its original position as it didn't move
1161+ // far enough to have it hidden
1162+ tryCompare(coverPage, "x", 0);
1163+ }
1164+
1165+ function test_dragToHide_data() {
1166+ return [
1167+ {tag: "left", startX: view.width * 0.95, endX: view.width * 0.1, hiddenX: -view.width},
1168+ {tag: "right", startX: view.width * 0.1, endX: view.width * 0.95, hiddenX: view.width},
1169+ ];
1170+ }
1171+ function test_dragToHide(data) {
1172+ var coverPage = findChild(view, "coverPage");
1173+ compare(coverPage.x, 0);
1174+ compare(coverPage.visible, true);
1175+ compare(coverPage.shown, true);
1176+ compare(coverPage.showProgress, 1);
1177+ compare(view.fullyShown, true);
1178+
1179+ touchFlick(view,
1180+ data.startX, view.height / 2, // start pos
1181+ data.endX, view.height / 2); // end pos
1182+
1183+ tryCompare(coverPage, "x", data.hiddenX);
1184+ tryCompare(coverPage, "visible", false);
1185+ tryCompare(coverPage, "shown", false);
1186+ tryCompare(coverPage, "showProgress", 0);
1187+ compare(view.fullyShown, false);
1188+ }
1189+
1190+ function test_hiddenViewRemainsHiddenAfterResize_data() {
1191+ return [
1192+ {tag: "left", startX: view.width * 0.95, endX: view.width * 0.1},
1193+ {tag: "right", startX: view.width * 0.1, endX: view.width * 0.95},
1194+ ];
1195+ }
1196+ function test_hiddenViewRemainsHiddenAfterResize(data) {
1197+ touchFlick(view,
1198+ data.startX, view.height / 2, // start pos
1199+ data.endX, view.height / 2); // end pos
1200+
1201+ var coverPage = findChild(view, "coverPage");
1202+ tryCompare(coverPage, "x", data.tag == "left" ? -view.width : view.width);
1203+ tryCompare(coverPage, "visible", false);
1204+ tryCompare(coverPage, "shown", false);
1205+ tryCompare(coverPage, "showProgress", 0);
1206+
1207+ // flip dimensions to simulate an orientation change
1208+ view.width = loader.height;
1209+ view.height = loader.width;
1210+
1211+ // All properties should remain consistent
1212+ tryCompare(coverPage, "x", data.tag == "left" ? -view.width : view.width);
1213+ tryCompare(coverPage, "visible", false);
1214+ tryCompare(coverPage, "shown", false);
1215+ tryCompare(coverPage, "showProgress", 0);
1216+ }
1217+
1218+ // Make sure that if user has a mouse, they can still get rid of cover page
1219+ function test_mouseClickHidesCoverPage() {
1220+ var coverPage = findChild(view, "coverPage");
1221+
1222+ verify(coverPage.shown);
1223+ mouseClick(coverPage, coverPage.width/2, coverPage.height - units.gu(2));
1224+ verify(!coverPage.shown);
1225+ }
1226+
1227+ function test_showErrorMessage() {
1228+ var coverPage = findChild(view, "coverPage");
1229+ var swipeHint = findChild(coverPage, "swipeHint");
1230+ var errorMessageAnimation = findInvisibleChild(coverPage, "errorMessageAnimation");
1231+
1232+ view.showErrorMessage("hello");
1233+ compare(swipeHint.text, "《 hello 》");
1234+ verify(errorMessageAnimation.running);
1235+ verify(swipeHint.opacityAnimation.running);
1236+
1237+ errorMessageAnimation.complete();
1238+ swipeHint.opacityAnimation.complete();
1239+ tryCompare(swipeHint, "text", "《 " + i18n.tr("Unlock") + " 》");
1240+ }
1241+
1242+ function test_keepPasswordOnRotation() {
1243+ /* Check if the password is kept when rotating
1244+ * the screen from portrait to landscape in mobile.
1245+ */
1246+ setUsageMode("phone");
1247+ LightDM.Greeter.authenticate("has-password");
1248+ view.locked = true;
1249+ swipeAwayCover();
1250+
1251+ let greeterPrompt = findChild(view, "greeterPrompt0");
1252+ tryCompare(greeterPrompt, "enteredText", "");
1253+ typeString("test");
1254+ tryCompare(greeterPrompt, "enteredText", "test");
1255+ view.orientation = Qt.LandscapeOrientation;
1256+ tryCompare(greeterPrompt, "enteredText", "test");
1257+ }
1258+
1259+ function test_infographicsShownOnRotation_data() {
1260+ return [
1261+ {
1262+ tag: "phone-portrait-singleuser",
1263+ multiUser: false,
1264+ usageMode: "phone",
1265+ orientation: Qt.PortraitOrientation,
1266+ coverPage: true,
1267+ lockscreen: false
1268+ },
1269+ {
1270+ tag: "phone-portrait-multiuser",
1271+ multiUser: true,
1272+ usageMode: "phone",
1273+ orientation: Qt.PortraitOrientation,
1274+ coverPage: false,
1275+ lockscreen: false
1276+ },
1277+ {
1278+ tag: "phone-landscape-singleuser",
1279+ multiUser: false,
1280+ usageMode: "phone",
1281+ orientation: Qt.LandscapeOrientation,
1282+ coverPage: true,
1283+ lockscreen: false
1284+ },
1285+ {
1286+ tag: "phone-landscape-multiuser",
1287+ multiUser: true,
1288+ usageMode: "phone",
1289+ orientation: Qt.LandscapeOrientation,
1290+ coverPage: false,
1291+ lockscreen: false
1292+ },
1293+ {
1294+ tag: "tablet-landscape-singleuser",
1295+ multiUser: false,
1296+ usageMode: "tablet",
1297+ orientation: Qt.LandscapeOrientation,
1298+ coverPage: true,
1299+ lockscreen: false
1300+ },
1301+ {
1302+ tag: "tablet-landscape-multiuser",
1303+ multiUser: true,
1304+ usageMode: "tablet",
1305+ orientation: Qt.LandscapeOrientation,
1306+ coverPage: false,
1307+ lockscreen: true
1308+ },
1309+ {
1310+ tag: "tablet-portrait-singleuser",
1311+ multiUser: false,
1312+ usageMode: "tablet",
1313+ orientation: Qt.PortraitOrientation,
1314+ coverPage: true,
1315+ lockscreen: false
1316+ },
1317+ {
1318+ tag: "tablet-portrait-multiuser",
1319+ multiUser: true,
1320+ usageMode: "tablet",
1321+ orientation: Qt.PortraitOrientation,
1322+ coverPage: false,
1323+ lockscreen: false
1324+ },
1325+ {
1326+ tag: "desktop-singleuser",
1327+ multiUser: false,
1328+ usageMode: "desktop",
1329+ orientation: Qt.LandscapeOrientation,
1330+ coverPage: false,
1331+ lockscreen: true
1332+ },
1333+ {
1334+ tag: "desktop-multiuser",
1335+ multiUser: true,
1336+ usageMode: "desktop",
1337+ orientation: Qt.LandscapeOrientation,
1338+ coverPage: false,
1339+ lockscreen: true
1340+ }
1341+ ]
1342+ }
1343+
1344+ function test_infographicsShownOnRotation(data) {
1345+ /* Check if infographics is shown in the correct
1346+ * places. This test runs in all the combinations
1347+ * of orientation and usage mode.
1348+ */
1349+ setUsageMode(data.usageMode);
1350+ view.orientation = data.orientation;
1351+ view.multiUser = data.multiUser;
1352+ view.locked = true;
1353+
1354+ let coverPage = findChild(view, "coverPage");
1355+ let lockscreen = findChild(view, "lockscreen");
1356+ let coverPageInfographics = findChild(coverPage, "infographicsLoader");
1357+ let lockscreenInfographics = findChild(lockscreen, "infographicsLoader");
1358+ verify(coverPageInfographics);
1359+ verify(lockscreenInfographics);
1360+
1361+ tryCompare(coverPageInfographics, "active", data.coverPage);
1362+ swipeAwayCover();
1363+ tryCompare(lockscreenInfographics, "active", data.lockscreen);
1364+ }
1365+ }
1366+}
tests/qmltests/Greeter/tst_NarrowView.qmldeleted−565
@@ -1,565 +0,0 @@
1−/*
2− * Copyright 2014 Canonical Ltd.
3− * Copyright 2021 UBports Foundation
4− *
5− * This program is free software; you can redistribute it and/or modify
6− * it under the terms of the GNU General Public License as published by
7− * the Free Software Foundation; version 3.
8− *
9− * This program is distributed in the hope that it will be useful,
10− * but WITHOUT ANY WARRANTY; without even the implied warranty of
11− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12− * GNU General Public License for more details.
13− *
14− * You should have received a copy of the GNU General Public License
15− * along with this program. If not, see <http://www.gnu.org/licenses/>.
16− */
17−
18−import QtQuick 2.4
19−import QtTest 1.0
20−import ".."
21−import "../../../qml/Greeter"
22−import LightDMController 0.1
23−import LightDM.FullLightDM 0.1 as LightDM
24−import Ubuntu.Components 1.3
25−import Ubuntu.Telephony 0.1 as Telephony
26−import Unity.Test 0.1 as UT
27−
28−Item {
29− id: root
30− width: units.gu(90)
31− height: units.gu(80)
32−
33− Component.onCompleted: theme.name = "Ubuntu.Components.Themes.SuruDark" // use the same theme as the real shell
34−
35− Binding {
36− target: LightDMController
37− property: "userMode"
38− value: "single"
39− }
40−
41− Row {
42− anchors.fill: parent
43− Loader {
44− id: loader
45− width: root.width - controls.width
46− height: parent.height
47−
48− property bool itemDestroyed: false
49− sourceComponent: Component {
50− NarrowView {
51− background: "/usr/share/backgrounds/warty-final-ubuntu.png"
52− userModel: LightDM.Users
53− infographicModel: LightDM.Infographic
54−
55− launcherOffset: parseFloat(launcherOffsetField.text)
56− currentIndex: parseInt(currentIndexField.text, 10)
57− delayMinutes: parseInt(delayMinutesField.text, 10)
58− panelHeight: parseFloat(panelHeightField.text)
59− locked: lockedCheckBox.checked
60− alphanumeric: alphanumericCheckBox.checked
61−
62− Component.onDestruction: {
63− loader.itemDestroyed = true
64− }
65−
66− onSelected: {
67− currentIndexField.text = index;
68− }
69− }
70− }
71− }
72−
73− Rectangle {
74− id: controls
75− color: theme.palette.normal.background
76− width: units.gu(40)
77− height: parent.height
78−
79− Column {
80− anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
81− spacing: units.gu(1)
82−
83− Row {
84− Button {
85− text: "Hide"
86− onClicked: loader.item.hide()
87− }
88− }
89− Row {
90− Button {
91− text: "Show Message"
92− onClicked: LightDMService.prompts.append(messageField.text, LightDMService.prompts.Message)
93− }
94− TextField {
95− id: messageField
96− width: units.gu(10)
97− text: ""
98− }
99− }
100− Row {
101− Button {
102− text: "Show Prompt"
103− onClicked: LightDMService.prompts.append(promptField.text, isSecretCheckBox.checked ? LightDMService.prompts.Secret : LightDMService.prompts.Question)
104− }
105− TextField {
106− id: promptField
107− width: units.gu(10)
108− text: ""
109− }
110− CheckBox {
111− id: isSecretCheckBox
112− }
113− Label {
114− text: "secret"
115− }
116− }
117− Row {
118− Button {
119− text: "Notify Auth Failure"
120− onClicked: {
121− loader.item.notifyAuthenticationFailed();
122− }
123− }
124− }
125− Row {
126− Button {
127− text: "Try To Unlock"
128− onClicked: loader.item.tryToUnlock(toTheRightCheckBox.checked)
129− }
130− CheckBox {
131− id: toTheRightCheckBox
132− }
133− Label {
134− text: "toTheRight"
135− }
136− }
137− Row {
138− TextField {
139− id: launcherOffsetField
140− width: units.gu(10)
141− text: "0"
142− }
143− Label {
144− text: "launcherOffset"
145− }
146− }
147− Row {
148− TextField {
149− id: currentIndexField
150− width: units.gu(10)
151− text: "0"
152− }
153− Label {
154− text: "currentIndex"
155− }
156− }
157− Row {
158− TextField {
159− id: delayMinutesField
160− width: units.gu(10)
161− text: "0"
162− }
163− Label {
164− text: "delayMinutes"
165− }
166− }
167− Row {
168− TextField {
169− id: panelHeightField
170− width: units.gu(10)
171− text: "0"
172− }
173− Label {
174− text: "panelHeight"
175− }
176− }
177− Row {
178− CheckBox {
179− id: lockedCheckBox
180− }
181− Label {
182− text: "locked"
183− }
184− }
185− Row {
186− CheckBox {
187− id: alphanumericCheckBox
188− checked: true
189− }
190− Label {
191− text: "alphanumeric"
192− }
193− }
194− Row {
195− Label {
196− text: "selected: " + selectedSpy.count
197− }
198− }
199− Row {
200− Label {
201− text: "responded: " + respondedSpy.count
202− }
203− }
204− Row {
205− Label {
206− text: "teased: " + teaseSpy.count
207− }
208− }
209− Row {
210− Label {
211− text: "emergency: " + emergencySpy.count
212− }
213− }
214− Row {
215− Button {
216− text: "Reload View"
217− onClicked: {
218− loader.active = false;
219− loader.active = true;
220− }
221− }
222− }
223− }
224− }
225− }
226−
227− Binding {
228− target: LightDM.Infographic
229− property: "username"
230− value: "has-password"
231− }
232−
233− SignalSpy {
234− id: selectedSpy
235− target: loader.item
236− signalName: "selected"
237− }
238−
239− SignalSpy {
240− id: respondedSpy
241− target: loader.item
242− signalName: "responded"
243− }
244−
245− SignalSpy {
246− id: teaseSpy
247− target: loader.item
248− signalName: "tease"
249− }
250−
251− SignalSpy {
252− id: emergencySpy
253− target: loader.item
254− signalName: "emergencyCall"
255− }
256−
257− SignalSpy {
258− id: infographicDataChangedSpy
259− target: LightDM.Infographic
260− signalName: "dataChanged"
261− }
262−
263− UT.UnityTestCase {
264− name: "NarrowView"
265− when: windowShown
266−
267− property Item view: loader.status === Loader.Ready ? loader.item : null
268−
269− function init() {
270− view.currentIndex = 0; // break binding with text field
271−
272− LightDM.Greeter.authenticate("no-password");
273− tryCompare(LightDMService.prompts, "count", 1);
274−
275− telepathyHelper.ready = true;
276− telepathyHelper.emergencyCallsAvailable = true;
277− selectedSpy.clear();
278− respondedSpy.clear();
279− teaseSpy.clear();
280− emergencySpy.clear();
281− infographicDataChangedSpy.clear();
282− }
283−
284− function cleanup() {
285− loader.itemDestroyed = false;
286− loader.active = false;
287− tryCompare(loader, "status", Loader.Null);
288− tryCompare(loader, "item", null);
289− tryCompare(loader, "itemDestroyed", true);
290− loader.active = true;
291− tryCompare(loader, "status", Loader.Ready);
292− removeTimeConstraintsFromSwipeAreas(loader.item);
293− }
294−
295− function swipeAwayCover(toTheRight) {
296− if (toTheRight === undefined) {
297− toTheRight = false;
298− }
299−
300− tryCompare(view, "fullyShown", true);
301− var touchY = view.height / 2;
302− if (toTheRight) {
303− touchFlick(view, 0, touchY, view.width, touchY);
304− } else {
305− touchFlick(view, view.width, touchY, 0, touchY);
306− }
307− var coverPage = findChild(view, "coverPage");
308− tryCompare(coverPage, "showProgress", 0);
309− waitForRendering(view);
310− }
311−
312− function test_tease_data() {
313− return [
314− {tag: "left", x: 0, offset: 0, count: 1},
315− {tag: "leftWithOffsetPass", x: 10, offset: 10, count: 1},
316− {tag: "leftWithOffsetFail", x: 9, offset: 10, count: 0},
317− {tag: "right", x: view.width, offset: 0, count: 1},
318− ]
319− }
320− function test_tease(data) {
321− view.dragHandleLeftMargin = data.offset;
322− tap(view, data.x, 0);
323− tryCompare(teaseSpy, "count", data.count);
324− }
325−
326− function test_customBackground() {
327− var lockscreenShade = findChild(view, "lockscreenShade");
328− var backgroundShade = findChild(view, "backgroundShade");
329−
330− compare(lockscreenShade.opacity, 0);
331− verify(!backgroundShade.visible);
332−
333− view.hasCustomBackground = true;
334− compare(lockscreenShade.opacity, 0.4);
335− verify(backgroundShade.visible);
336− }
337−
338− function test_respondedWithPin() {
339− LightDM.Greeter.authenticate("has-pin");
340− view.locked = true;
341− view.alphanumeric = false;
342− swipeAwayCover();
343− typeString("1234");
344− respondedSpy.wait();
345− compare(respondedSpy.signalArguments[0][0], "1234");
346− }
347−
348− function test_respondedWithPassphrase() {
349− LightDM.Greeter.authenticate("has-password");
350− view.locked = true;
351− swipeAwayCover();
352− typeString("test");
353− keyClick(Qt.Key_Enter);
354− compare(respondedSpy.count, 1);
355− compare(respondedSpy.signalArguments[0][0], "test");
356− }
357−
358− function test_respondedWithSwipe_data() {
359− return [
360− {tag: "left", toTheRight: false, hiddenX: -view.width},
361− {tag: "right", toTheRight: true, hiddenX: view.width},
362− ];
363− }
364− function test_respondedWithSwipe(data) {
365− swipeAwayCover(data.toTheRight);
366− var coverPage = findChild(view, "coverPage");
367− compare(coverPage.x, data.hiddenX);
368− compare(respondedSpy.count, 1);
369− compare(respondedSpy.signalArguments[0][0], "");
370− }
371−
372− function test_emergencyCall_data() {
373− return [
374− {tag: "phone", available: true},
375− {tag: "desktop", available: false},
376− ];
377− }
378− function test_emergencyCall(data) {
379− telepathyHelper.emergencyCallsAvailable = data.available;
380− view.locked = true;
381− swipeAwayCover();
382− var emergencyCallLabel = findChild(view, "emergencyCallLabel");
383− tap(emergencyCallLabel);
384− if (!data.available) {
385− expectFail("", "Bug 1616538 prevents us supporting conditional emergency button support");
386− }
387− compare(emergencyCallLabel.visible, data.available ? true : false);
388− compare(emergencySpy.count, data.available ? 1 : 0);
389− }
390−
391− function test_emergencyCallAvailability() {
392− view.locked = true;
393− var emergencyCallLabel = findChild(view, "emergencyCallLabel");
394− verify(emergencyCallLabel.visible);
395−
396− telepathyHelper.emergencyCallsAvailable = false;
397− telepathyHelper.ready = true;
398− expectFail("", "Bug 1616538 prevents us supporting conditional emergency button support");
399− verify(!emergencyCallLabel.visible);
400−
401− telepathyHelper.emergencyCallsAvailable = true;
402− telepathyHelper.ready = false;
403− verify(!emergencyCallLabel.visible);
404− }
405−
406− function test_fullyShown() {
407− tryCompare(view, "fullyShown", true);
408− swipeAwayCover();
409− tryCompare(view, "fullyShown", false);
410− view.locked = true;
411− tryCompare(view, "fullyShown", true);
412− view.locked = false;
413− tryCompare(view, "fullyShown", false);
414− }
415−
416− function test_required() {
417− tryCompare(view, "required", true);
418− swipeAwayCover();
419− tryCompare(view, "required", false);
420− view.locked = true;
421− tryCompare(view, "required", true);
422− view.locked = false;
423− tryCompare(view, "required", false);
424− }
425−
426− function test_tryToUnlock() {
427− var coverPage = findChild(view, "coverPage");
428− tryCompare(coverPage, "showProgress", 1);
429− compare(view.tryToUnlock(false), true);
430− tryCompare(coverPage, "showProgress", 0);
431− compare(view.tryToUnlock(false), false);
432− }
433−
434− /*
435− Regression test for https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1388359
436− "User metrics can no longer be changed by double tap"
437− */
438− function test_doubleTapSwitchesToNextInfographic() {
439− var infographicPrivate = findInvisibleChild(view, "infographicPrivate");
440− verify(infographicPrivate);
441−
442− // wait for the UI to settle down before double tapping it
443− tryCompare(infographicPrivate, "animating", false);
444−
445− var dataCircle = findChild(view, "dataCircle");
446− verify(dataCircle);
447−
448− mouseDoubleClickSequence(dataCircle);
449−
450− tryCompare(infographicDataChangedSpy, "count", 1);
451− }
452−
453− function test_movesBackIntoPlaceWhenNotDraggedFarEnough() {
454− var coverPage = findChild(view, "coverPage");
455−
456− var dragEvaluator = findInvisibleChild(coverPage, "edgeDragEvaluator");
457− verify(dragEvaluator);
458−
459− // Make it easier to get a rejection/rollback. Otherwise would have to inject
460− // a fake timer into dragEvaluator.
461− // Afterall, we are testing if the CoverPage indeed moves back on a
462− // rollback decision, not the drag evaluation itself.
463− dragEvaluator.minDragDistance = dragEvaluator.maxDragDistance / 2;
464−
465− // it starts as fully shown
466− compare(coverPage.x, 0);
467−
468− // then we drag it a bit
469− var startX = coverPage.width - 1;
470− var touchY = coverPage.height / 2;
471− var dragXDelta = -(dragEvaluator.minDragDistance * 0.3);
472− touchFlick(coverPage,
473− startX , touchY, // start pos
474− startX + dragXDelta, touchY, // end pos
475− true /* beginTouch */, false /* endTouch */);
476−
477− // which should make it move a bit
478− tryCompareFunction(function() {return coverPage.x < 0;}, true);
479−
480− // then we release it
481− touchRelease(coverPage, startX + dragXDelta, touchY);
482−
483− // which should make it move back into its original position as it didn't move
484− // far enough to have it hidden
485− tryCompare(coverPage, "x", 0);
486− }
487−
488− function test_dragToHide_data() {
489− return [
490− {tag: "left", startX: view.width * 0.95, endX: view.width * 0.1, hiddenX: -view.width},
491− {tag: "right", startX: view.width * 0.1, endX: view.width * 0.95, hiddenX: view.width},
492− ];
493− }
494− function test_dragToHide(data) {
495− var coverPage = findChild(view, "coverPage");
496− compare(coverPage.x, 0);
497− compare(coverPage.visible, true);
498− compare(coverPage.shown, true);
499− compare(coverPage.showProgress, 1);
500− compare(view.fullyShown, true);
501−
502− touchFlick(view,
503− data.startX, view.height / 2, // start pos
504− data.endX, view.height / 2); // end pos
505−
506− tryCompare(coverPage, "x", data.hiddenX);
507− tryCompare(coverPage, "visible", false);
508− tryCompare(coverPage, "shown", false);
509− tryCompare(coverPage, "showProgress", 0);
510− compare(view.fullyShown, false);
511− }
512−
513− function test_hiddenViewRemainsHiddenAfterResize_data() {
514− return [
515− {tag: "left", startX: view.width * 0.95, endX: view.width * 0.1},
516− {tag: "right", startX: view.width * 0.1, endX: view.width * 0.95},
517− ];
518− }
519− function test_hiddenViewRemainsHiddenAfterResize(data) {
520− touchFlick(view,
521− data.startX, view.height / 2, // start pos
522− data.endX, view.height / 2); // end pos
523−
524− var coverPage = findChild(view, "coverPage");
525− tryCompare(coverPage, "x", data.tag == "left" ? -view.width : view.width);
526− tryCompare(coverPage, "visible", false);
527− tryCompare(coverPage, "shown", false);
528− tryCompare(coverPage, "showProgress", 0);
529−
530− // flip dimensions to simulate an orientation change
531− view.width = loader.height;
532− view.height = loader.width;
533−
534− // All properties should remain consistent
535− tryCompare(coverPage, "x", data.tag == "left" ? -view.width : view.width);
536− tryCompare(coverPage, "visible", false);
537− tryCompare(coverPage, "shown", false);
538− tryCompare(coverPage, "showProgress", 0);
539− }
540−
541− // Make sure that if user has a mouse, they can still get rid of cover page
542− function test_mouseClickHidesCoverPage() {
543− var coverPage = findChild(view, "coverPage");
544−
545− verify(coverPage.shown);
546− mouseClick(coverPage, coverPage.width/2, coverPage.height - units.gu(2));
547− verify(!coverPage.shown);
548− }
549−
550− function test_showErrorMessage() {
551− var coverPage = findChild(view, "coverPage");
552− var swipeHint = findChild(coverPage, "swipeHint");
553− var errorMessageAnimation = findInvisibleChild(coverPage, "errorMessageAnimation");
554−
555− view.showErrorMessage("hello");
556− compare(swipeHint.text, "《 hello 》");
557− verify(errorMessageAnimation.running);
558− verify(swipeHint.opacityAnimation.running);
559−
560− errorMessageAnimation.complete();
561− swipeHint.opacityAnimation.complete();
562− tryCompare(swipeHint, "text", "《 " + i18n.tr("Unlock") + " 》");
563− }
564− }
565−}
tests/qmltests/Greeter/tst_WideView.qmldeleted−781
@@ -1,781 +0,0 @@
1−/*
2− * Copyright 2014-2016 Canonical Ltd.
3− * Copyright 2021 UBports Foundation
4− *
5− * This program is free software; you can redistribute it and/or modify
6− * it under the terms of the GNU General Public License as published by
7− * the Free Software Foundation; version 3.
8− *
9− * This program is distributed in the hope that it will be useful,
10− * but WITHOUT ANY WARRANTY; without even the implied warranty of
11− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12− * GNU General Public License for more details.
13− *
14− * You should have received a copy of the GNU General Public License
15− * along with this program. If not, see <http://www.gnu.org/licenses/>.
16− */
17−
18−import QtQuick 2.4
19−import QtTest 1.0
20−import ".."
21−import "../../../qml/Greeter"
22−import LightDMController 0.1
23−import LightDM.FullLightDM 0.1 as LightDM
24−import Ubuntu.Components 1.3
25−import Unity.Test 0.1 as UT
26−
27−StyledItem {
28− id: root
29−
30− width: units.gu(120)
31− height: units.gu(80)
32− focus: true
33−
34− theme.name: "Ubuntu.Components.Themes.Ambiance"
35−
36− Row {
37− anchors.fill: parent
38− Loader {
39− id: loader
40− width: root.width - controls.width
41− height: parent.height
42− focus: true
43−
44− property bool itemDestroyed: false
45− sourceComponent: Component {
46− WideView {
47− id: view
48−
49− background: "/usr/share/backgrounds/warty-final-ubuntu.png"
50− userModel: LightDM.Users
51− infographicModel: LightDM.Infographic
52−
53− launcherOffset: parseFloat(launcherOffsetField.text)
54− currentIndex: parseInt(currentIndexField.text, 10)
55− delayMinutes: parseInt(delayMinutesField.text, 10)
56− panelHeight: parseFloat(panelHeightField.text)
57− locked: lockedCheckBox.checked
58− inputMethod: fakeInputMethod
59−
60− Component.onDestruction: {
61− loader.itemDestroyed = true
62− }
63−
64− onSelected: {
65− if (index >= 0)
66− currentIndexField.text = index;
67− }
68−
69− QtObject {
70− id: fakeInputMethod
71− property bool visible: fakeKeyboard.visible
72− property var keyboardRectangle: QtObject {
73− property real x: fakeKeyboard.x
74− property real y: fakeKeyboard.y
75− property real width: fakeKeyboard.width
76− property real height: fakeKeyboard.height
77− }
78− }
79−
80− Rectangle {
81− id: fakeKeyboard
82− color: "green"
83− opacity: 0.7
84− anchors.bottom: view.bottom
85− width: view.width
86− height: view.height * 0.6
87− visible: keyboardVisibleCheckBox.checked
88− Text {
89− text: "Keyboard Rectangle"
90− color: "yellow"
91− font.bold: true
92− fontSizeMode: Text.Fit
93− minimumPixelSize: 10; font.pixelSize: 200
94− verticalAlignment: Text.AlignVCenter
95− x: (parent.width - width) / 2
96− y: (parent.height - height) / 2
97− width: parent.width
98− height: parent.height
99− }
100− }
101−
102− }
103− }
104− }
105−
106− Rectangle {
107− id: controls
108− color: theme.palette.normal.background
109− width: units.gu(40)
110− height: parent.height
111− Column {
112− anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
113− spacing: units.gu(1)
114− Row {
115− Button {
116− text: "Hide"
117− onClicked: loader.item.hide()
118− }
119− }
120− Row {
121− Button {
122− text: "Show Message"
123− onClicked: LightDMService.prompts.append(messageField.text, LightDMService.prompts.Message)
124− }
125− TextField {
126− id: messageField
127− width: units.gu(10)
128− text: ""
129− }
130− }
131− Row {
132− Button {
133− text: "Show Prompt"
134− onClicked: LightDMService.prompts.append(promptField.text, isSecretCheckBox.checked ? LightDMService.prompts.Secret : LightDMService.prompts.Question)
135− }
136− TextField {
137− id: promptField
138− width: units.gu(10)
139− text: ""
140− }
141− CheckBox {
142− id: isSecretCheckBox
143− }
144− Label {
145− text: "secret"
146− }
147− }
148− Row {
149− Button {
150− text: "Notify Auth Failure"
151− onClicked: {
152− loader.item.notifyAuthenticationFailed();
153− }
154− }
155− }
156− Row {
157− Button {
158− text: "Try To Unlock"
159− onClicked: loader.item.tryToUnlock(toTheRightCheckBox.checked)
160− }
161− CheckBox {
162− id: toTheRightCheckBox
163− }
164− Label {
165− text: "toTheRight"
166− }
167− }
168− Row {
169− TextField {
170− id: launcherOffsetField
171− width: units.gu(10)
172− text: "0"
173− }
174− Label {
175− text: "launcherOffset"
176− }
177− }
178− Row {
179− TextField {
180− id: currentIndexField
181− width: units.gu(10)
182− text: "0"
183− }
184− Label {
185− text: "currentIndex"
186− }
187− }
188− Row {
189− TextField {
190− id: delayMinutesField
191− width: units.gu(10)
192− text: "0"
193− }
194− Label {
195− text: "delayMinutes"
196− }
197− }
198− Row {
199− TextField {
200− id: panelHeightField
201− width: units.gu(10)
202− text: "0"
203− }
204− Label {
205− text: "panelHeight"
206− }
207− }
208− Row {
209− CheckBox {
210− id: lockedCheckBox
211− }
212− Label {
213− text: "locked"
214− }
215− }
216− Row {
217− Label {
218− text: "selected: " + selectedSpy.count
219− }
220− }
221− Row {
222− Label {
223− text: "responded: " + respondedSpy.count
224− }
225− }
226− Row {
227− Label {
228− text: "teased: " + teaseSpy.count
229− }
230− }
231− Row {
232− Label {
233− text: "emergency: " + emergencySpy.count
234− }
235− }
236− Row {
237− id: multipleSessions
238− CheckBox {
239− id: multipleSessionsCheckbox
240− onClicked: {
241− if (checked) {
242− LightDMController.sessionMode = "full";
243− } else {
244− LightDMController.sessionMode = "single";
245− }
246− }
247− Connections {
248− target: LightDMController
249− onSessionModeChanged: {
250− if (LightDMController.sessionMode === "full") {
251− multipleSessionsCheckbox.checked = true;
252− } else {
253− multipleSessionsCheckbox.checked = false;
254− }
255− }
256− }
257− }
258− Label {
259− text: "Multiple Sessions"
260− }
261− }
262− Row {
263− Slider {
264− id: numSessionsSlider
265−
266− width: units.gu(10)
267− minimumValue: 0
268− maximumValue: LightDMController.numAvailableSessions
269− value: LightDMController.numSessions
270− visible: LightDMController.sessionMode === "full"
271− Binding {
272− target: LightDMController
273− property: "numSessions"
274− value: numSessionsSlider.value
275− }
276− }
277− Label {
278− text: "Available Sessions"
279− }
280− }
281− Row {
282− Button {
283− text: "Reload View"
284− onClicked: {
285− loader.active = false;
286− loader.active = true;
287− }
288− }
289− }
290− Row {
291− CheckBox {
292− id: keyboardVisibleCheckBox
293− }
294− Label {
295− text: "Keyboard Visible"
296− }
297− }
298− }
299− }
300− }
301−
302− SignalSpy {
303− id: selectedSpy
304− target: loader.item
305− signalName: "selected"
306− }
307−
308− SignalSpy {
309− id: respondedSpy
310− target: loader.item
311− signalName: "responded"
312− }
313−
314− SignalSpy {
315− id: teaseSpy
316− target: loader.item
317− signalName: "tease"
318− }
319−
320− SignalSpy {
321− id: emergencySpy
322− target: loader.item
323− signalName: "emergencyCall"
324− }
325−
326− UT.UnityTestCase {
327− name: "WideView"
328− when: windowShown
329−
330− property Item view: loader.status === Loader.Ready ? loader.item : null
331− property url testIconDirectory: Qt.resolvedUrl("../../../qml/Greeter/graphics/session_icons")
332−
333− function init() {
334− selectIndex(0); // break binding with text field
335− tryCompare(LightDMService.prompts, "count", 1);
336− selectedSpy.clear();
337− respondedSpy.clear();
338− teaseSpy.clear();
339− emergencySpy.clear();
340− LightDMController.reset();
341− LightDM.Sessions.iconSearchDirectories = [testIconDirectory];
342−
343− waitForRendering(view);
344− var userList = findChild(view, "userList");
345− tryCompare(userList, "movingInternally", false);
346− }
347−
348− function cleanup() {
349− keyboardVisibleCheckBox.checked = false;
350−
351− loader.itemDestroyed = false;
352− loader.active = false;
353− tryCompare(loader, "status", Loader.Null);
354− tryCompare(loader, "item", null);
355− tryCompare(loader, "itemDestroyed", true);
356− loader.active = true;
357− tryCompare(loader, "status", Loader.Ready);
358− removeTimeConstraintsFromSwipeAreas(loader.item);
359− LightDMController.sessionMode = "single";
360− }
361−
362− function getIndexOf(name) {
363− for (var i = 0; i < LightDM.Users.count; i++) {
364− if (name === LightDM.Users.data(i, LightDM.UserRoles.NameRole)) {
365− return i;
366− }
367− }
368− fail("Didn't find name")
369− return -1;
370− }
371−
372− function selectIndex(i) {
373− var user = LightDM.Users.data(i, LightDM.UserRoles.NameRole);
374− LightDM.Greeter.authenticate(user);
375−
376− var userList = findChild(view, "userList");
377− var promptList = findChild(view, "promptList");
378−
379− view.currentIndex = i;
380− tryCompare(promptList, "opacity", 1);
381− tryCompare(userList, "movingInternally", false);
382− }
383−
384− function selectUser(name) {
385− var i = getIndexOf(name);
386− selectIndex(i);
387− return i;
388− }
389−
390− function swipeAwayCover() {
391− tryCompare(view, "fullyShown", true);
392− var touchY = view.height / 2;
393− touchFlick(view, view.width, touchY, 0, touchY);
394− var coverPage = findChild(view, "coverPage");
395− tryCompare(coverPage, "showProgress", 0);
396− waitForRendering(view);
397− }
398−
399− function test_sessionless_user_is_still_valid() {
400− var loginList = findChild(view, "loginList")
401−
402− /*
403− * If a user has never logged in before, or, for some reason
404− * has no sessionHint, ensure that the model returns the default
405− * session and that the view respects this
406− */
407− selectUser("no-session");
408− compare(LightDM.Users.data(loginList.currentIndex, LightDM.UserRoles.SessionRole), LightDM.Greeter.defaultSession);
409−
410− tryCompare(loginList, "currentSession", LightDM.Greeter.defaultSession);
411− }
412−
413− function test_changingSessionSticksToUser() {
414− var loginList = findChild(view, "loginList");
415−
416− selectUser("invalid-session");
417− tryCompare(loginList, "currentSession", "invalid");
418−
419− selectUser("has-password");
420− tryCompare(loginList, "currentSession", "ubuntu");
421−
422− selectUser("invalid-session")
423− tryCompare(loginList, "currentSession", "invalid");
424− }
425−
426− function test_tease_data() {
427− return [
428− {tag: "locked", x: 0, offset: 0, count: 0, locked: true},
429− {tag: "left", x: 0, offset: 0, count: 1, locked: false},
430− {tag: "leftWithOffsetPass", x: 10, offset: 10, count: 1, locked: false},
431− {tag: "leftWithOffsetFail", x: 9, offset: 10, count: 0, locked: false},
432− {tag: "right", x: view.width, offset: 0, count: 1, locked: false},
433− ]
434− }
435−
436− function test_sessionIconsAreValid() {
437− LightDMController.sessionMode = "full";
438− selectUser("has-password");
439−
440− // Test the login list icon is valid
441− var sessionChooserButton = findChild(view, "sessionChooserButton");
442− compare(sessionChooserButton.visible, true);
443−
444− var loginList = findChild(view, "loginList");
445− var session = String(loginList.currentSession).toLowerCase();
446− var icon = String(sessionChooserButton.icon);
447− compare(icon.indexOf(session) > -1, true);
448−
449− // Test the session list icons are valid
450− var coverPage = findChild(view, "coverPage");
451− coverPage.state = "SessionsList"
452− var sessionsList = findChild(view, "sessionsList");
453− tryCompare(sessionsList, "visible", true);
454− }
455−
456− function test_choosingNewSessionChangesLoginListIcon() {
457− // Ensure the default session is selected (Ubuntu)
458− cleanup();
459−
460− LightDMController.sessionMode = "full";
461− selectUser("has-password");
462−
463− var sessionChooserButton = findChild(view, "sessionChooserButton");
464− var icon = String(sessionChooserButton.icon);
465− compare(icon.indexOf("ubuntu") > -1, true);
466−
467− tap(sessionChooserButton)
468− for(var i = 0; i < LightDM.Sessions.count; i++) {
469− var delegateName = "sessionDelegate" + String(i);
470− var currentDelegate = findChild(view, delegateName);
471− var sessionKey = LightDM.Sessions.data(i,LightDM.SessionRoles.KeyRole);
472− if (sessionKey === "gnome-classic") {
473− waitForRendering(currentDelegate);
474− tap(currentDelegate);
475− waitForRendering(sessionChooserButton);
476− break;
477− }
478− }
479−
480− icon = String(sessionChooserButton.icon);
481− compare(icon.indexOf("gnome") > -1, true,
482− "Expected icon to contain gnome but it was " + icon);
483− }
484−
485− function test_noSessionsDoesntBreakView() {
486− LightDMController.sessionMode = "none";
487− compare(LightDM.Sessions.count, 0)
488− }
489−
490− function test_sessionIconNotShownWithOneSession() {
491− LightDMController.sessionMode = "single";
492− compare(LightDM.Sessions.count, 1);
493−
494− var sessionChooserButton = findChild(view, "sessionChooserButton");
495− tryCompare(sessionChooserButton, "visible", false);
496− }
497−
498− function test_sessionIconNotShownWithActiveUser() {
499− selectUser("active");
500−
501− var sessionChooserButton = findChild(view, "sessionChooserButton");
502− tryCompare(sessionChooserButton, "visible", false);
503− }
504−
505− function test_sessionIconShownWithMultipleSessions() {
506− LightDMController.sessionMode = "full";
507− selectUser("has-password");
508−
509− var sessionChooserButton = findChild(view, "sessionChooserButton");
510− tryCompare(sessionChooserButton, "visible", true);
511− }
512−
513− function test_tease(data) {
514− view.locked = data.locked;
515− view.dragHandleLeftMargin = data.offset;
516− tap(view, data.x, 0);
517− compare(teaseSpy.count, data.count);
518− }
519−
520− function test_selected() {
521− var delegate = findChild(view, "username2");
522− tap(delegate);
523− compare(selectedSpy.count, 1);
524− compare(selectedSpy.signalArguments[0][0], 2);
525− compare(view.currentIndex, 0); // confirm we didn't change
526− }
527−
528− function test_customBackground() {
529− var coverPage = findChild(view, "coverPage");
530− var backgroundShade = findChild(coverPage, "backgroundShade");
531−
532− verify(!view.hasCustomBackground);
533− verify(!backgroundShade.visible);
534−
535− view.hasCustomBackground = true;
536− verify(backgroundShade.visible);
537− }
538−
539− function test_respondedWithPassword() {
540− selectUser("has-password");
541− var greeterPrompt = findChild(view, "greeterPrompt0");
542− verify(greeterPrompt.isSecret);
543− typeString("password");
544− keyClick(Qt.Key_Enter);
545− compare(respondedSpy.count, 1);
546− compare(respondedSpy.signalArguments[0][0], "password");
547− }
548−
549− function test_respondedWithNonSecret() {
550− selectUser("question-prompt");
551− var greeterPrompt = findChild(view, "greeterPrompt0");
552− verify(!greeterPrompt.isSecret);
553− typeString("foo");
554− keyClick(Qt.Key_Enter);
555− compare(respondedSpy.count, 1);
556− compare(respondedSpy.signalArguments[0][0], "foo");
557− }
558−
559− function test_respondedWithSwipe() {
560− swipeAwayCover();
561− compare(respondedSpy.count, 1);
562− compare(respondedSpy.signalArguments[0][0], "");
563− }
564−
565− function test_fullyShown() {
566− tryCompare(view, "fullyShown", true);
567− swipeAwayCover();
568− tryCompare(view, "fullyShown", false);
569− }
570−
571− function test_required() {
572− tryCompare(view, "required", true);
573− swipeAwayCover();
574− tryCompare(view, "required", false);
575− }
576−
577− function test_infoPrompt() {
578− selectUser("info-prompt");
579−
580− var infoLabel = findChild(view, "infoLabel0");
581− compare(infoLabel.text, "Welcome to Unity Greeter");
582− compare(infoLabel.textFormat, Text.PlainText);
583−
584− verify(findChild(view, "greeterPrompt1") != null);
585− }
586−
587− function test_longInfoPrompt() {
588− selectUser("long-info-prompt");
589−
590− var infoLabel = findChild(view, "infoLabel0");
591− 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.");
592− verify(infoLabel.contentWidth > infoLabel.width);
593−
594− verify(findChild(view, "greeterPrompt1") != null);
595− }
596−
597− function test_multiInfoPrompt() {
598− selectUser("multi-info-prompt");
599−
600− var infoLabel0 = findChild(view, "infoLabel0");
601− compare(infoLabel0.text, "Welcome to Unity Greeter");
602−
603− var infoLabel1 = findChild(view, "infoLabel1");
604− compare(infoLabel1.text, "This is an error");
605− compare(infoLabel1.color, theme.palette.normal.negative);
606−
607− var infoLabel2 = findChild(view, "infoLabel2");
608− compare(infoLabel2.text, "You should have seen three messages");
609−
610− verify(findChild(view, "greeterPrompt3") != null);
611− }
612−
613− // Escape is used to reset the authentication, especially if PAM is unresponsive
614− function test_escape() {
615− var index = selectUser("has-password");
616− selectedSpy.clear();
617− view.locked = true;
618− var promptField = findChild(view, "promptField");
619− tap(promptField);
620− verify(promptField.activeFocus);
621− compare(promptField.opacity, 1);
622−
623− typeString("password");
624− keyClick(Qt.Key_Enter);
625− verify(promptField.activeFocus);
626− compare(promptField.opacity, 0); // hidden by fakeLabel
627−
628− compare(selectedSpy.count, 0);
629− keyClick(Qt.Key_Escape);
630− compare(selectedSpy.count, 1);
631− compare(selectedSpy.signalArguments[0][0], index);
632−
633− selectIndex(index);
634− var promptField = findChild(view, "promptField");
635− verify(promptField.activeFocus);
636− compare(promptField.opacity, 1);
637− }
638−
639− function test_unicode() {
640− var index = selectUser("unicode");
641− var label = findChild(view, "username" + index);
642− tryCompare(label, "text", "가나다라마");
643− }
644−
645− function test_authError() {
646− var index = selectUser("auth-error");
647− var greeterPrompt = findChild(view, "greeterPrompt1"); // after error message
648− view.locked = true;
649− compare(greeterPrompt.text, "Retry");
650− tap(greeterPrompt);
651− compare(respondedSpy.count, 0);
652− compare(selectedSpy.count, 1);
653− compare(selectedSpy.signalArguments[0][0], index);
654− }
655−
656− function test_noPassword() {
657− selectUser("no-password");
658− var greeterPrompt = findChild(view, "greeterPrompt0");
659− compare(greeterPrompt.text, "Log In");
660− tap(greeterPrompt);
661− compare(selectedSpy.count, 0);
662− compare(respondedSpy.count, 1);
663− compare(respondedSpy.signalArguments[0][0], "");
664− }
665−
666− function test_loginListNotCoveredByKeyboard() {
667− var loginList = findChild(view, "loginList");
668− compare(loginList.height, view.height);
669−
670− // when the vkb shows up, loginList is moved up to remain fully uncovered
671−
672− keyboardVisibleCheckBox.checked = true;
673−
674− var halfway = (view.height - loginList.highlightedHeight) / 2;
675− var halfwayWithOsk = halfway - view.inputMethod.keyboardRectangle.height / 2;
676− tryCompare(loginList, "boxVerticalOffset", halfwayWithOsk);
677−
678− var highlightItem = findChild(loginList, "highlightItem");
679− tryCompareFunction( function() {
680− var highlightRect = highlightItem.mapToItem(view, 0, 0, highlightItem.width, highlightItem.height);
681− return highlightRect.y + highlightRect.height <= view.inputMethod.keyboardRectangle.y;
682− }, true);
683−
684− // once the vkb goes away, loginList goes back to its full height
685−
686− keyboardVisibleCheckBox.checked = false;
687−
688− tryCompare(loginList, "boxVerticalOffset", halfway);
689− }
690−
691− function test_passphrase() {
692− var index = selectUser("has-password");
693− var promptField = findChild(view, "promptField");
694−
695− verify(view.alphanumeric);
696− compare(promptField.inputMethodHints & Qt.ImhDigitsOnly, 0);
697−
698− keyClick(Qt.Key_D);
699− compare(promptField.text, "d");
700− }
701−
702− function test_passcode() {
703− var index = selectUser("has-pin");
704− var promptField = findChild(view, "promptField");
705−
706− view.alphanumeric = false;
707− compare(promptField.inputMethodHints & Qt.ImhDigitsOnly, Qt.ImhDigitsOnly);
708−
709− keyClick(Qt.Key_D);
710− compare(promptField.text, "");
711−
712− keyClick(Qt.Key_0);
713− keyClick(Qt.Key_0);
714− keyClick(Qt.Key_0);
715− keyClick(Qt.Key_0);
716− compare(promptField.text, "0000");
717−
718− compare(respondedSpy.count, 1);
719− compare(respondedSpy.signalArguments[0][0], "0000");
720−
721− compare(promptField.opacity, 0);
722− }
723−
724− function test_loginListMovement_data() {
725− return [
726− {tag: "up", key: Qt.Key_Up, result: 1},
727− {tag: "down", key: Qt.Key_Down, result: 3},
728− ]
729− }
730−
731− function test_loginListMovement(data) {
732− selectIndex(2);
733− selectedSpy.clear();
734−
735− keyClick(data.key);
736− compare(selectedSpy.count, 1);
737− compare(selectedSpy.signalArguments[0][0], data.result);
738− }
739−
740− function test_focusStaysActive() {
741− selectUser("no-password");
742− var greeterPrompt = findChild(view, "greeterPrompt0");
743− verify(greeterPrompt.activeFocus);
744− keyClick(Qt.Key_Enter);
745− compare(selectedSpy.count, 0);
746− compare(respondedSpy.count, 1);
747− compare(respondedSpy.signalArguments[0][0], "");
748− verify(greeterPrompt.activeFocus);
749− keyClick(Qt.Key_Enter);
750− compare(respondedSpy.count, 1);
751−
752− selectUser("has-password");
753− var greeterPrompt = findChild(view, "greeterPrompt0");
754− verify(greeterPrompt.activeFocus);
755− keyClick(Qt.Key_D);
756− keyClick(Qt.Key_Enter);
757− compare(selectedSpy.count, 0);
758− compare(respondedSpy.count, 2);
759− compare(respondedSpy.signalArguments[1][0], "d");
760− verify(greeterPrompt.activeFocus);
761− keyClick(Qt.Key_Enter);
762− compare(respondedSpy.count, 2);
763−
764− var index = selectUser("no-password");
765− var greeterPrompt = findChild(view, "greeterPrompt0");
766− view.locked = true;
767− verify(greeterPrompt.activeFocus);
768− keyClick(Qt.Key_Enter);
769− compare(respondedSpy.count, 2);
770− compare(selectedSpy.count, 1);
771− compare(selectedSpy.signalArguments[0][0], index);
772− verify(greeterPrompt.activeFocus);
773− keyClick(Qt.Key_Enter);
774− compare(selectedSpy.count, 1);
775−
776− selectUser("no-password");
777− var greeterPrompt = findChild(view, "greeterPrompt0");
778− verify(greeterPrompt.activeFocus);
779− }
780− }
781−}
tests/qmltests/tst_OrientedShell.qml+45−41
@@ -40,11 +40,12 @@ Rectangle {
4040 height: units.gu(100)
4141
4242 property var tryShell: null
43+ property string ldmUserMode: "single"
4344
4445 Binding {
4546 target: LightDMController
4647 property: "userMode"
47− value: "single"
48+ value: ldmUserMode
4849 }
4950
5051 QtObject {
@@ -240,6 +241,23 @@ Rectangle {
240241 LightDM.Greeter.showGreeter();
241242 }
242243 }
244+
245+
246+ Label {
247+ text: "LightDM mock mode"
248+ }
249+
250+ ListItem.ItemSelector {
251+ anchors { left: parent.left; right: parent.right }
252+ activeFocusOnPress: false
253+ model: ["single", "single-passphrase", "single-pin", "full"]
254+ onSelectedIndexChanged: {
255+ testCase.tearDown();
256+ ldmUserMode = model[selectedIndex];
257+ testCase.init();
258+ }
259+ }
260+
243261 Label {
244262 text: "Physical Orientation:"
245263 }
@@ -601,43 +619,6 @@ Rectangle {
601619 compare(shell.transformRotationAngle, root.primaryOrientationAngle + 180);
602620 }
603621
604− function test_greeterRemainsInPrimaryOrientation_data() {
605− return [
606− {tag: "mako", deviceName: "mako"},
607− {tag: "manta", deviceName: "manta"},
608− {tag: "flo", deviceName: "flo"}
609− ];
610− }
611− function test_greeterRemainsInPrimaryOrientation(data) {
612− var orientedShell = loadShell(data.deviceName);
613− var shell = findChild(orientedShell, "shell");
614−
615− var gmailApp = ApplicationManager.startApplication("gmail-webapp");
616− verify(gmailApp);
617−
618− // ensure the mock gmail-webapp is as we expect
619− compare(gmailApp.rotatesWindowContents, false);
620− compare(gmailApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
621− | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
622−
623− // wait until it's able to rotate
624− tryCompare(shell, "orientationChangesEnabled", true);
625−
626− compare(shell.transformRotationAngle, root.primaryOrientationAngle);
627− rotateTo(90, orientedShell);
628− tryCompare(shell, "transformRotationAngle", root.primaryOrientationAngle + 90);
629−
630− showGreeter(orientedShell);
631−
632− tryCompare(shell, "transformRotationAngle", root.primaryOrientationAngle);
633− rotateTo(180, orientedShell);
634− compare(shell.transformRotationAngle, root.primaryOrientationAngle);
635− rotateTo(270, orientedShell);
636− compare(shell.transformRotationAngle, root.primaryOrientationAngle);
637− rotateTo(0, orientedShell);
638− compare(shell.transformRotationAngle, root.primaryOrientationAngle);
639− }
640−
641622 function test_appRotatesWindowContents_data() {
642623 return [
643624 {tag: "mako", deviceName: "mako", orientationAngleAfterRotation: 90},
@@ -1229,7 +1210,7 @@ Rectangle {
12291210
12301211 showGreeter(orientedShell);
12311212
1232− tryCompare(shell, "transformRotationAngle", root.primaryOrientationAngle);
1213+ tryCompare(shell, "transformRotationAngle", root.primaryOrientationAngle + 90);
12331214 }
12341215
12351216 /*
@@ -1482,7 +1463,8 @@ Rectangle {
14821463 touchFlick(shell, touchX, touchY, orientedShell.width * 0.1, touchY);
14831464
14841465 // wait until the animation has finished
1485− tryCompare(greeter, "shown", false);
1466+ if (LightDMController.userMode == "single")
1467+ tryCompare(greeter, "shown", false);
14861468 waitForRendering(greeter);
14871469 }
14881470
@@ -1493,9 +1475,11 @@ Rectangle {
14931475 tryCompare(greeter, "fullyShown", true);
14941476 }
14951477
1496− function loadShell(deviceName) {
1478+ function loadShell(deviceName, userMode = "single") {
14971479 applicationArguments.deviceName = deviceName;
14981480
1481+ ldmUserMode = userMode; // Set the mode for LightDM ( default is "single" )
1482+
14991483 // reload our test subject to get it in a fresh state once again
15001484 var orientedShell = createTemporaryObject(shellComponent, shellRect);
15011485
@@ -1697,5 +1681,25 @@ Rectangle {
16971681 MockInputDeviceBackend.removeDevice("/touchscreen");
16981682 tryCompare(tutorial, "paused", true);
16991683 }
1684+
1685+ /* Check if the keyboard icon on the greeter screen
1686+ * is only shown when an external keyboard is attached
1687+ * and if it is hidden when no keyboard is attached.
1688+ */
1689+ function test_greeterKeyboardDetection() {
1690+ var orientedShell = loadShell("mako", "single-passphrase");
1691+ MockInputDeviceBackend.removeDevice("/indicator_kbd0");
1692+
1693+ var greeterPrompt = findChild(orientedShell, "greeterPrompt0");
1694+ verify(greeterPrompt);
1695+
1696+ var promptKeyboard = findChild(greeterPrompt, "greeterPromptKeyboardButton");
1697+
1698+ tryCompare(promptKeyboard, "visible", false);
1699+
1700+ MockInputDeviceBackend.addMockDevice("/kbd0", InputInfo.Keyboard);
1701+
1702+ tryCompare(promptKeyboard, "visible", true);
1703+ }
17001704 }
17011705}
tests/qmltests/tst_Shell.qml+48−15
@@ -703,6 +703,32 @@ Rectangle {
703703 mockNotificationsModel.append(n)
704704 }
705705
706+ function swipeAwayGreeterCover() {
707+ var greeter = findChild(shell, "greeter");
708+
709+ if (!greeter.shown) {
710+ console.log("Greeter not shown. Not swiping.");
711+ return;
712+ }
713+
714+ var greeterView = findChild(greeter, "GreeterView");
715+ var lockscreen = findChild(greeter, "lockscreen");
716+
717+ tryCompare(lockscreen, "shown", true);
718+
719+ tryCompare(greeter, "fullyShown", true);
720+ waitForGreeterToStabilize();
721+ removeTimeConstraintsFromSwipeAreas(greeter);
722+
723+ var touchX = shell.width - (shell.edgeSize / 2);
724+ var touchY = shell.height / 2;
725+ touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY);
726+
727+ var coverPage = findChild(greeterView, "coverPage");
728+ tryCompare(coverPage, "showProgress", 0);
729+ waitForRendering(greeterView);
730+ }
731+
706732 function swipeAwayGreeter() {
707733 var greeter = findChild(shell, "greeter");
708734
@@ -763,13 +789,16 @@ Rectangle {
763789 var greeter = findChild(shell, "greeter")
764790 tryCompare(greeter, "fullyShown", true);
765791
766− var promptButton = findChild(greeter, "promptButton");
767− tryCompare(promptButton, "visible", isButton);
768−
769− var promptField = findChild(greeter, "promptField");
770− tryCompare(promptField, "visible", !isButton);
771−
772− mouseClick(promptButton);
792+ if (isButton) {
793+ var promptButton = findChild(greeter, "promptButton");
794+ verify(promptButton);
795+ tryCompare(promptButton, "visible", true);
796+ mouseClick(promptButton);
797+ } else {
798+ var promptField = findChild(greeter, "promptField");
799+ verify(promptField);
800+ tryCompare(promptField, "visible", true);
801+ }
773802 }
774803
775804 function confirmLoggedIn(loggedIn) {
@@ -993,14 +1022,13 @@ Rectangle {
9931022 }
9941023
9951024 function test_greeterStartsCorrectSession() {
996− loadShell("desktop");
9971025 setLightDMMockMode("full");
1026+ loadShell("desktop");
9981027
9991028 LightDMController.sessionMode = "full"
10001029 LightDMController.numSessions = LightDMController.numAvailableSessions;
10011030 var greeter = findChild(shell, "greeter");
1002− var view = findChild(greeter, "WideView");
1003− verify(view, "This test requires WideView to be loaded");
1031+ var view = findChild(greeter, "GreeterView");
10041032
10051033 var loginList = findChild(view, "loginList");
10061034
@@ -1400,8 +1428,9 @@ Rectangle {
14001428 }
14011429
14021430 function test_tabletLogin(data) {
1403− loadShell("tablet");
14041431 setLightDMMockMode("full");
1432+ loadShell("tablet");
1433+ swipeAwayGreeterCover();
14051434
14061435 selectUser(data.user);
14071436
@@ -1424,8 +1453,10 @@ Rectangle {
14241453 }
14251454
14261455 function test_appLaunchDuringGreeter(data) {
1427− loadShell("tablet");
14281456 setLightDMMockMode("full");
1457+ loadShell("tablet");
1458+
1459+ swipeAwayGreeterCover();
14291460
14301461 selectUser(data.user)
14311462
@@ -1441,9 +1472,11 @@ Rectangle {
14411472 }
14421473
14431474 function test_manualLoginFlow() {
1444− loadShell("desktop");
1445− LightDMController.showManualLoginHint = true;
14461475 setLightDMMockMode("full");
1476+ LightDMController.showManualLoginHint = true;
1477+ loadShell("desktop");
1478+
1479+ swipeAwayGreeterCover();
14471480
14481481 var i = selectUser("*other");
14491482 var greeter = findChild(shell, "greeter");
@@ -2580,8 +2613,8 @@ Rectangle {
25802613
25812614 function test_fullShellModeHasNoInitialGreeter() {
25822615 shellRect.mode = "full-shell";
2583− loadShell("phone");
25842616 setLightDMMockMode("single-pin");
2617+ loadShell("phone");
25852618 waitForRendering(shell);
25862619
25872620 var greeter = findChild(shell, "greeter");