on xenial help sign in
61 files changed+3469−2594
since splitsnapshots
unifiedsplit
.gitignore+1 README.md+18−38 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/de.po+3−3 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/ja.po+15−18 po/km.po+11−14 po/nb.po+4−6 po/nl.po+1−1 po/pt.po+4−4 po/sc.po+6−6 po/sv.po+30−60 po/th.po+20−20 po/ur.po+31−28 qml/Components/ItemGrabber.qml+1−1 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
.gitignore+1
@@ -13,3 +13,4 @@
1313/.kdev4/
1414/build/
1515/*.kdev4
16+.clickable/
README.md+18−38
@@ -1,51 +1,31 @@
1−# Unity8
1+# Lomiri
22
3−A convergent desktop environment.
3+Lomiri is the operating environment for everywhere. It is able to span the gaps between touch, mouse, and keyboard; between phones, tablets, and workstations; and look good doing it.
44
5+If you want to contribute to Lomiri on Ubuntu Touch, you've come to the right place. If you want to package Lomiri for other operating systems, you'll be better served by the main branch on the repository that we're migrating to: https://gitlab.com/ubports/core/lomiri
56
6−[![Unity 8](http://ubuntufun.de/wp-content/uploads/2016/05/screenshot20160518_232322980.png)](https://unity8.io)
7+Lomiri has been renamed from Unity8. Most of the software on-device in Ubuntu Touch based on Ubuntu 16.04 is still called `unity8`.
78
9+## Where to report issues
810
9−### How to install
11+Issues related to the version of Lomiri that currently runs on Ubuntu Touch devices may still be reported on this repository. We will move these reports to the GitLab repository as part of our final migration.
1012
11−The latest development preview of Unity8 for desktop can be installed on Ubuntu 16.04 (xenial) and 18.04 (bionic) as follows:
13+## Developing Lomiri on Ubuntu Touch
1214
13−1. Run the install script
14−```
15−bash <(wget -qO- https://raw.githubusercontent.com/ubports/unity8-desktop-install-tools/master/install.sh)
16−```
17−2. Reboot, select "unity8" at the login prompt, and enjoy :)
15+This repository contains the code for Lomiri currently running on Ubuntu Touch. If you would like to modify this version of Lomiri, follow the steps below. If you would like to modify Lomiri in another operating system, check out our new repository: https://gitlab.com/ubports/core/lomiri
1816
17+Lomiri can be built and its test suite run using [`clickable`](https://clickable-ut.dev). This is a convenient method to try out most graphical changes to Lomiri without an annoying redeployment process. If you wish to test your changes on your device or make changes that are more difficult to test without real hardware, check out [Making changes and testing locally on the UBports documentation](https://docs.ubports.com/en/latest/systemdev/testing-locally.html). If not, read on.
1918
20−**NOTE:** The above installation instructions currently only work on Ubuntu 16.04 (xenial) and 18.04 (bionic). Please be aware that the script will install the latest *development preview* of Unity8 for desktop. ***Expect bugs!*** Only install this preview if you understand the risks and are willing/able to fix your system if the preview breaks anything. You are advised *not* to install this preview onto a production system.
19+Before you start, [install Clickable](https://clickable-ut.dev/en/latest/install.html).
2120
22−See [CODING](CODING) for build instructions.
21+Now, clone this repository to your computer: `git clone https://github.com/ubports/unity8.git`
2322
23+Move into this directory: `cd unity8`
2424
25−### Where to report issues
25+Now you can use the full suite of tools provided by this repository's [clickable.yaml](clickable.yaml). For example:
2626
27−Issues related to the version of Unity8 for desktop (the latest development preview) installed using the above instructions should be reported on the [Unity8 issue tracker](https://github.com/ubports/unity8/issues) in *this* repository.
28−
29−Note that issues related to the version of Unity8 that currently runs on Ubuntu Touch devices should *NOT* be reported in this repository, but instead should be reported on the [Ubuntu Touch issue tracker](https://github.com/ubports/ubuntu-touch/issues). This will change once Ubuntu Touch moves to the latest version of Unity8.
30−
31−
32−### What works / what does not work
33−
34−*This list is incomplete, please help improve it*
35−
36−What works:
37−- Most Wayland apps work
38−- Many report being able to log in using LightDM, but may require a few attempts
39−
40−Does not work:
41−- Most Xapps don't (yet) work
42−- Proprietary drivers don't (yet) work
43−- Many report being unable to log in using GDM
44−
45−
46−### Get involved
47−
48−* [Kanban-Board](https://github.com/ubports/unity8/projects/1)
49−* If you are interesting in helping us in any way you can join [Unity8 Forum](https://forums.ubports.com/category/36/unity8).
50−* For reporting bugs or filing feature-requests, please use the project’s [issues page](https://github.com/ubports/unity8/issues).
51−* For IM you can join our Telegram group [for Developer users](https://t.me/UBports_Unity8).
27+* `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 --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...
30+* `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.
31+* `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.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/de.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-04-22 05:43+0000\n"
12−"Last-Translator: Daniel Frost <one@frostinfo.de>\n"
11+"PO-Revision-Date: 2021-07-24 19:07+0000\n"
12+"Last-Translator: Stefan Kalb <stefan.kalb@gmx.ch>\n"
1313"Language-Team: German <https://translate.ubports.com/projects/ubports/lomiri/"
1414"de/>\n"
1515"Language: de\n"
@@ -655,7 +655,7 @@ msgstr "Bevorzugter Name"
655655
656656#: qml/Wizard/Pages/70-passwd-type.qml:40
657657msgid "Lock Screen"
658−msgstr "Sperrbildschirm"
658+msgstr "Bildschirm sperren"
659659
660660#: qml/Wizard/Pages/70-passwd-type.qml:103
661661msgctxt "Label: Type of security method"
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/ja.po+15−18
@@ -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-05-09 19:51+0000\n"
12−"Last-Translator: M.Sugahara <equaaqua@hotmail.com>\n"
11+"PO-Revision-Date: 2021-08-10 18:11+0000\n"
12+"Last-Translator: Oyamada Jun <oyamada.junjun@outlook.jp>\n"
1313"Language-Team: Japanese <https://translate.ubports.com/projects/ubports/"
1414"lomiri/ja/>\n"
1515"Language: ja\n"
@@ -278,11 +278,11 @@ msgstr "Ctrl + Super + Down"
278278
279279#: qml/Components/KeyboardShortcutsOverlay.qml:323
280280msgid "Minimizes or restores the current window."
281−msgstr ""
281+msgstr "現在のウィンドウを最小化したり、復元したりします。"
282282
283283#: qml/Components/KeyboardShortcutsOverlay.qml:331
284284msgid "Ctrl + Super + Left or Right"
285−msgstr ""
285+msgstr "Ctrl + Super + Left or Right"
286286
287287#: qml/Components/KeyboardShortcutsOverlay.qml:336
288288msgid "Semi-maximizes the current window."
@@ -342,7 +342,7 @@ msgstr ""
342342
343343#: qml/Components/ModeSwitchWarningDialog.qml:94
344344msgid "Close all"
345−msgstr ""
345+msgstr "すべて閉じる"
346346
347347#: qml/Components/SharingPicker.qml:54
348348msgid "Preview Share Item"
@@ -412,7 +412,7 @@ msgstr "パスコードを入力"
412412
413413#: qml/Greeter/NarrowView.qml:235
414414msgid "Emergency"
415−msgstr ""
415+msgstr "緊急"
416416
417417#: qml/Greeter/PromptList.qml:126
418418msgid "Passphrase"
@@ -420,11 +420,11 @@ msgstr ""
420420
421421#: qml/Greeter/PromptList.qml:126
422422msgid "Passcode"
423−msgstr ""
423+msgstr "パスコード"
424424
425425#: qml/Greeter/SessionsList.qml:124
426426msgid "Select desktop environment"
427−msgstr ""
427+msgstr "デスクトップ環境を選択"
428428
429429#: qml/Launcher/Drawer.qml:250
430430#, fuzzy
@@ -460,32 +460,29 @@ msgstr "再生するものがありません"
460460#, qt-format
461461msgid "%1 hour"
462462msgid_plural "%1 hours"
463−msgstr[0] ""
464−msgstr[1] ""
463+msgstr[0] "%1 時間"
465464
466465#: qml/Panel/Indicators/IndicatorMenuItemFactory.qml:1001
467466#, qt-format
468467msgid "%1 minute"
469468msgid_plural "%1 minutes"
470−msgstr[0] ""
471−msgstr[1] ""
469+msgstr[0] "%1 分"
472470
473471#: qml/Panel/Indicators/IndicatorMenuItemFactory.qml:1006
474472#, qt-format
475473msgid "%1 second"
476474msgid_plural "%1 seconds"
477−msgstr[0] ""
478−msgstr[1] ""
475+msgstr[0] "%1 秒"
479476
480477#: qml/Panel/Indicators/IndicatorMenuItemFactory.qml:1009
481478msgid "0 seconds"
482−msgstr ""
479+msgstr "0 秒"
483480
484481#. Translators: String like "1 hour, 2 minutes, 3 seconds remaining"
485482#: qml/Panel/Indicators/IndicatorMenuItemFactory.qml:1011
486483#, qt-format
487484msgid "%1 remaining"
488−msgstr ""
485+msgstr "残り %1"
489486
490487#: qml/Panel/Indicators/IndicatorMenuItemFactory.qml:1017
491488msgid "In queue…"
@@ -515,12 +512,12 @@ msgstr "失敗しました、再試行するにはタップしてください"
515512#: qml/Panel/Indicators/MessageMenuItemFactory.qml:210
516513msgctxt "Button: Send a reply message"
517514msgid "Send"
518−msgstr ""
515+msgstr "送信"
519516
520517#: qml/Panel/Indicators/MessageMenuItemFactory.qml:152
521518msgctxt "Label: Hint in message indicator line edit"
522519msgid "Reply"
523−msgstr ""
520+msgstr "返信"
524521
525522#: qml/Panel/Indicators/MessageMenuItemFactory.qml:209
526523msgctxt "Button: Call back on phone"
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+4−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-03-12 02:36+0000\n"
12−"Last-Translator: Jan Christian Sherdahl <jan@sherdahl.no>\n"
11+"PO-Revision-Date: 2021-10-15 10:11+0000\n"
12+"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"
1515"Language: 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"
@@ -142,7 +140,7 @@ msgstr "Tastatursnarveier"
142140
143141#: qml/Components/KeyboardShortcutsOverlay.qml:55
144142msgid "Lomiri"
145−msgstr ""
143+msgstr "Lomiri"
146144
147145#: qml/Components/KeyboardShortcutsOverlay.qml:62
148146msgid "PrtScr"
po/nl.po+1−1
@@ -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-06-30 19:36+0000\n"
11+"PO-Revision-Date: 2021-09-21 19:30+0000\n"
1212"Last-Translator: Heimen Stoffels <vistausss@fastmail.com>\n"
1313"Language-Team: Dutch <https://translate.ubports.com/projects/ubports/lomiri/"
1414"nl/>\n"
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+30−60
@@ -8,16 +8,16 @@ 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-02-13 13:06+0000\n"
12−"Last-Translator: Rondy Andersson <rondarius@vivaldi.net>\n"
13−"Language-Team: Swedish <https://translate.ubports.com/projects/unity8/unity8/"
14−"sv/>\n"
11+"PO-Revision-Date: 2021-10-19 20:55+0000\n"
12+"Last-Translator: Jakob Fridesjö <jakob.fridesjo@tuta.io>\n"
13+"Language-Team: Swedish <https://translate.ubports.com/projects/ubports/"
14+"lomiri/sv/>\n"
1515"Language: sv\n"
1616"MIME-Version: 1.0\n"
1717"Content-Type: text/plain; charset=UTF-8\n"
1818"Content-Transfer-Encoding: 8bit\n"
1919"Plural-Forms: nplurals=2; plural=n != 1;\n"
20−"X-Generator: Weblate 3.8\n"
20+"X-Generator: Weblate 3.11.3\n"
2121"X-Launchpad-Export-Date: 2016-11-23 06:36+0000\n"
2222
2323#: plugins/LightDM/Greeter.cpp:199
@@ -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…"
@@ -548,11 +526,8 @@ msgid "Call back"
548526msgstr "Ring upp"
549527
550528#: qml/Panel/PanelMenuPage.qml:92
551−#, fuzzy
552−#| msgctxt "Button: Go back one page in the Wizard"
553−#| msgid "Back"
554529msgid "Back"
555−msgstr "Bakåt"
530+msgstr "Backa"
556531
557532#: qml/Stage/SideStage.qml:76
558533msgid "Drag using 3 fingers any application from one window to the other"
@@ -560,13 +535,11 @@ msgstr "Dra ett program från ett fönster till det andra med tre fingrar"
560535
561536#: qml/Stage/Stage.qml:671
562537msgid "No running apps"
563−msgstr ""
538+msgstr "Inga appar körs"
564539
565540#: qml/Tutorial/TutorialLeftLong.qml:49
566−#, fuzzy
567−#| msgid "Long swipe from the left edge to open the Today scope"
568541msgid "Long swipe from the left edge to open the Application Drawer"
569−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"
570543
571544#: qml/Tutorial/TutorialLeft.qml:47
572545msgid "Short swipe from the left edge to open the launcher"
@@ -618,7 +591,7 @@ msgstr "Vad är nytt"
618591
619592#: qml/Wizard/Pages/11-changelog.qml:74
620593msgid "Loading..."
621−msgstr "Laddar…"
594+msgstr "Läser in…"
622595
623596#: qml/Wizard/Pages/20-keyboard.qml:31
624597msgid "Select Keyboard"
@@ -702,12 +675,9 @@ msgstr ""
702675"kommer enheten att startas om automatiskt."
703676
704677#: qml/Wizard/Pages/79-system-update.qml:76
705−#, fuzzy
706−#| msgctxt "string identifying name of the update"
707−#| msgid "Ubuntu system"
708678msgctxt "string identifying name of the update"
709679msgid "Ubuntu Touch system"
710−msgstr "Ubuntusystem"
680+msgstr "Ubuntu Touch system"
711681
712682#: qml/Wizard/Pages/79-system-update.qml:83
713683#, 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/Components/ItemGrabber.qml+1−1
@@ -35,7 +35,7 @@ Rectangle {
3535
3636 NotificationAudio {
3737 id: shutterSound
38− source: "/system/media/audio/ui/camera_click.ogg"
38+ source: "/usr/share/sounds/ubports/camera/click/camera_click.ogg"
3939 }
4040
4141 function capture(item) {
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");