on xenial help sign in
[TLWM] Change rootFocus to a property

This changes rootFocus to a qt property insted of a callable function. This makes the API make more sense in QML. In addition we add some C++ tests for the rootFocus behavior. There are already some roundabout tests for this behavior in the qmltests for the different Stages, but this is more direct.

commitedcd7106f15f08ab1a525d427e471feed061f784 authorMarius Gripsgard <marius@ubports.com> dateMon 2 Dec 2019, 17:50 (7 years ago) parent2a9d044
5 files changed+97−9
plugins/WindowManager/TopLevelWindowModel.cpp+10−1 plugins/WindowManager/TopLevelWindowModel.h+24−5 qml/Stage/Stage.qml+1−1 tests/plugins/WindowManager/tst_TopLevelWindowModel.cpp+60 tests/qmltests/Stage/tst_PhoneStage.qml+2−2
plugins/WindowManager/TopLevelWindowModel.cpp+10−1
@@ -40,6 +40,9 @@ namespace unityapi = unity::shell::application;
4040TopLevelWindowModel::TopLevelWindowModel()
4141 : m_nullWindow(createWindow(nullptr))
4242{
43+ connect(m_nullWindow, &Window::focusedChanged, this, [this] {
44+ Q_EMIT rootFocusChanged();
45+ });
4346}
4447
4548void TopLevelWindowModel::setApplicationManager(unityapi::ApplicationManagerInterface* value)
@@ -744,8 +747,14 @@ void TopLevelWindowModel::closeAllWindows()
744747 }
745748}
746749
747−void TopLevelWindowModel::rootFocus(bool focus)
750+bool TopLevelWindowModel::rootFocus()
751+{
752+ return !m_nullWindow->focused();
753+}
754+
755+void TopLevelWindowModel::setRootFocus(bool focus)
748756{
757+ INFO_MSG << "(" << focus << ")";
749758 if (focus) {
750759 // Give focus back to previous focused window, only if null window is focused.
751760 // If null window is not focused, a different app had taken the focus and we
plugins/WindowManager/TopLevelWindowModel.h+24−5
@@ -88,6 +88,24 @@ class WINDOWMANAGERQML_EXPORT TopLevelWindowModel : public QAbstractListModel
8888 */
8989 Q_PROPERTY(int nextId READ nextId NOTIFY nextIdChanged)
9090
91+
92+ /**
93+ * @brief Sets whether a user Window or "nothing" should be focused
94+ *
95+ * This implementation of TLWM must have something focused. However, the
96+ * user may wish to have nothing in some cases - for example, when they
97+ * minimize all their windows on the desktop or unfocus the app they're
98+ * using by clicking the background or indicators.
99+ *
100+ * Unsetting rootFocus effectively focuses "nothing" by setting up a Window
101+ * that has no displayable Surfaces and bringing it into focus.
102+ *
103+ * Setting rootFocus attempts to focus the Window which was focused last -
104+ * unless another app is attempting to gain focus (as determined by
105+ * pendingActivation) and that's why we got rootFocus.
106+ */
107+ Q_PROPERTY(bool rootFocus READ rootFocus WRITE setRootFocus NOTIFY rootFocusChanged)
108+
91109public:
92110 /**
93111 * @brief The Roles supported by the model
@@ -164,11 +182,6 @@ public:
164182 */
165183 Q_INVOKABLE void raiseId(int id);
166184
167− /**
168− * @brief Raises and focuses a window with no surface
169− */
170− Q_INVOKABLE void rootFocus(bool focus);
171−
172185 /**
173186 * @brief Closes all windows, emits closedAllWindows when done
174187 */
@@ -178,6 +191,10 @@ public:
178191 * @brief Sets pending activation flag
179192 */
180193 Q_INVOKABLE void pendingActivation();
194+
195+ void setRootFocus(bool focus);
196+ bool rootFocus();
197+
181198Q_SIGNALS:
182199 void countChanged();
183200 void inputMethodSurfaceChanged(unity::shell::application::MirSurfaceInterface* inputMethodSurface);
@@ -196,6 +213,8 @@ Q_SIGNALS:
196213
197214 void closedAllWindows();
198215
216+ void rootFocusChanged();
217+
199218private Q_SLOTS:
200219 void onSurfaceCreated(unity::shell::application::MirSurfaceInterface *surface);
201220 void onSurfacesRaised(const QVector<unity::shell::application::MirSurfaceInterface*> &surfaces);
qml/Stage/Stage.qml+1−1
@@ -100,7 +100,7 @@ FocusScope {
100100 // this will not happen if a pending activation is going on
101101 // - If not interactive: Activate nullWindow, this makes
102102 // sure none of the apps have focus when stage is not active
103− topLevelSurfaceList.rootFocus(interactive);
103+ topLevelSurfaceList.rootFocus = interactive;
104104 }
105105
106106 onAltTabPressedChanged: {
tests/plugins/WindowManager/tst_TopLevelWindowModel.cpp+60
@@ -15,6 +15,7 @@
1515 */
1616
1717#include <QtTest/QtTest>
18+#include <QSignalSpy>
1819
1920// WindowManager plugin
2021#include <TopLevelWindowModel.h>
@@ -32,6 +33,8 @@ private Q_SLOTS:
3233
3334 void singleSurfaceStartsHidden();
3435 void secondSurfaceIsHidden();
36+ void rootFocus();
37+ void rootFocusInhibit();
3538
3639private:
3740 ApplicationManager *applicationManager{nullptr};
@@ -125,6 +128,63 @@ void tst_TopLevelWindowModel::secondSurfaceIsHidden()
125128 QCOMPARE((void*)topLevelWindowModel->windowAt(0)->surface(), (void*)firstSurface);
126129}
127130
131+// Ensure that rootFocus unfocuses and refocuses windows
132+void tst_TopLevelWindowModel::rootFocus()
133+{
134+ applicationManager->startApplication(QString("hello-world"), QStringList());
135+
136+ // We need to keep the window's surface null, unlike the other tests,
137+ // because then the TLWM assumes that the window is unknown to MirAL and
138+ // handles focus changes itself. If MirAL and QtMir are behaving properly,
139+ // they mirror this behavior for windows with surfaces.
140+ QCOMPARE(topLevelWindowModel->rowCount(), 1);
141+ QCOMPARE((void*)topLevelWindowModel->windowAt(0)->surface(), (void*)nullptr);
142+ auto dummyWindow = topLevelWindowModel->windowAt(0);
143+
144+ auto rootFocusChangedSpy = new QSignalSpy(topLevelWindowModel, &TopLevelWindowModel::rootFocusChanged);
145+
146+ // Unsetting rootFocus will remove focus from the dummy window
147+ topLevelWindowModel->setRootFocus(false);
148+ QVERIFY(!topLevelWindowModel->rootFocus());
149+ QVERIFY(!dummyWindow->focused());
150+ QCOMPARE(rootFocusChangedSpy->count(), 1);
151+
152+ // Setting rootFocus will add focus back to the dummy window
153+ topLevelWindowModel->setRootFocus(true);
154+ QVERIFY(topLevelWindowModel->rootFocus());
155+ QCOMPARE((void*)topLevelWindowModel->focusedWindow(), (void*)dummyWindow);
156+ QCOMPARE(rootFocusChangedSpy->count(), 2);
157+}
158+
159+// Ensure that rootFocus does not refocus a window if we focus another
160+void tst_TopLevelWindowModel::rootFocusInhibit()
161+{
162+ applicationManager->startApplication(QString("1"), QStringList());
163+
164+ auto dummyWindow1 = topLevelWindowModel->windowAt(0);
165+
166+ // Unsetting rootFocus will remove focus from the dummy window
167+ topLevelWindowModel->setRootFocus(false);
168+ QVERIFY(!topLevelWindowModel->rootFocus());
169+ QVERIFY(!dummyWindow1->focused());
170+
171+ // Starting an Application will cause a pendingActivation, setting
172+ // rootFocus but preventing the original window from becoming refocused
173+ auto dummyWindow1FocusSpy = new QSignalSpy(dummyWindow1, &Window::focusedChanged);
174+
175+ // Saying that dummyWindow1 is different than dummyWindow2 will have to do
176+ // since Applications don't know their Windows and we don't mock the other
177+ // way around.
178+ applicationManager->startApplication(QString("2"), QStringList());
179+ auto dummyWindow2 = topLevelWindowModel->windowAt(0);
180+ QVERIFY((void*)dummyWindow1 != (void*)dummyWindow2);
181+
182+ QVERIFY(topLevelWindowModel->rootFocus());
183+ QVERIFY(!dummyWindow1->focused());
184+ QVERIFY(dummyWindow2->focused());
185+ QVERIFY(dummyWindow1FocusSpy->empty());
186+}
187+
128188QTEST_MAIN(tst_TopLevelWindowModel)
129189
130190#include "tst_TopLevelWindowModel.moc"
tests/qmltests/Stage/tst_PhoneStage.qml+2−2
@@ -507,7 +507,7 @@ Item {
507507 waitForRendering(stage);
508508
509509 // Give nullwindow focus
510− topLevelSurfaceList.rootFocus(false);
510+ topLevelSurfaceList.rootFocus = false;
511511 var webbrowserSurfaceId = topLevelSurfaceList.nextId;
512512 topLevelSurfaceList.pendingActivation();
513513 var webbrowserApp = ApplicationManager.startApplication("morph-browser");
@@ -515,7 +515,7 @@ Item {
515515 var webbrowserWindow = topLevelSurfaceList.windowAt(0);
516516
517517 // Give focus back to stage, but we should NOT refocus prev window (dash in this case)
518− topLevelSurfaceList.rootFocus(true);
518+ topLevelSurfaceList.rootFocus = true;
519519
520520 // We should have given focus to webbrowser at this point, not dashWindow
521521 tryCompare(topLevelSurfaceList, "focusedWindow", webbrowserWindow);