on xenial help sign in
[TLWM] Change rootFocus to a property
edcd710Marius Gripsgard
plugins/WindowManager/TopLevelWindowModel.cpp+10−1
@@ -40,6 +40,9 @@ namespace unityapi = unity::shell::application;
40TopLevelWindowModel::TopLevelWindowModel()
40TopLevelWindowModel::TopLevelWindowModel()
41 : m_nullWindow(createWindow(nullptr))
41 : m_nullWindow(createWindow(nullptr))
42{
42{
43+ connect(m_nullWindow, &Window::focusedChanged, this, [this] {
44+ Q_EMIT rootFocusChanged();
45+ });
43}
46}
44
47
45void TopLevelWindowModel::setApplicationManager(unityapi::ApplicationManagerInterface* value)
48void TopLevelWindowModel::setApplicationManager(unityapi::ApplicationManagerInterface* value)
@@ -744,8 +747,14 @@ void TopLevelWindowModel::closeAllWindows()
744 }
747 }
745}
748}
746
749
747−void TopLevelWindowModel::rootFocus(bool focus)
750+bool TopLevelWindowModel::rootFocus()
751+{
752+ return !m_nullWindow->focused();
753+}
754+
755+void TopLevelWindowModel::setRootFocus(bool focus)
748{
756{
757+ INFO_MSG << "(" << focus << ")";
749 if (focus) {
758 if (focus) {
750 // Give focus back to previous focused window, only if null window is focused.
759 // Give focus back to previous focused window, only if null window is focused.
751 // If null window is not focused, a different app had taken the focus and we
760 // 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
88 */
88 */
89 Q_PROPERTY(int nextId READ nextId NOTIFY nextIdChanged)
89 Q_PROPERTY(int nextId READ nextId NOTIFY nextIdChanged)
90
90
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+
91public:
109public:
92 /**
110 /**
93 * @brief The Roles supported by the model
111 * @brief The Roles supported by the model
@@ -164,11 +182,6 @@ public:
164 */
182 */
165 Q_INVOKABLE void raiseId(int id);
183 Q_INVOKABLE void raiseId(int id);
166
184
167− /**
168− * @brief Raises and focuses a window with no surface
169− */
170− Q_INVOKABLE void rootFocus(bool focus);
171−
172 /**
185 /**
173 * @brief Closes all windows, emits closedAllWindows when done
186 * @brief Closes all windows, emits closedAllWindows when done
174 */
187 */
@@ -178,6 +191,10 @@ public:
178 * @brief Sets pending activation flag
191 * @brief Sets pending activation flag
179 */
192 */
180 Q_INVOKABLE void pendingActivation();
193 Q_INVOKABLE void pendingActivation();
194+
195+ void setRootFocus(bool focus);
196+ bool rootFocus();
197+
181Q_SIGNALS:
198Q_SIGNALS:
182 void countChanged();
199 void countChanged();
183 void inputMethodSurfaceChanged(unity::shell::application::MirSurfaceInterface* inputMethodSurface);
200 void inputMethodSurfaceChanged(unity::shell::application::MirSurfaceInterface* inputMethodSurface);
@@ -196,6 +213,8 @@ Q_SIGNALS:
196
213
197 void closedAllWindows();
214 void closedAllWindows();
198
215
216+ void rootFocusChanged();
217+
199private Q_SLOTS:
218private Q_SLOTS:
200 void onSurfaceCreated(unity::shell::application::MirSurfaceInterface *surface);
219 void onSurfaceCreated(unity::shell::application::MirSurfaceInterface *surface);
201 void onSurfacesRaised(const QVector<unity::shell::application::MirSurfaceInterface*> &surfaces);
220 void onSurfacesRaised(const QVector<unity::shell::application::MirSurfaceInterface*> &surfaces);
qml/Stage/Stage.qml+1−1
@@ -100,7 +100,7 @@ FocusScope {
100 // this will not happen if a pending activation is going on
100 // this will not happen if a pending activation is going on
101 // - If not interactive: Activate nullWindow, this makes
101 // - If not interactive: Activate nullWindow, this makes
102 // sure none of the apps have focus when stage is not active
102 // sure none of the apps have focus when stage is not active
103− topLevelSurfaceList.rootFocus(interactive);
103+ topLevelSurfaceList.rootFocus = interactive;
104 }
104 }
105
105
106 onAltTabPressedChanged: {
106 onAltTabPressedChanged: {
tests/plugins/WindowManager/tst_TopLevelWindowModel.cpp+60
@@ -15,6 +15,7 @@
15 */
15 */
16
16
17#include <QtTest/QtTest>
17#include <QtTest/QtTest>
18+#include <QSignalSpy>
18
19
19// WindowManager plugin
20// WindowManager plugin
20#include <TopLevelWindowModel.h>
21#include <TopLevelWindowModel.h>
@@ -32,6 +33,8 @@ private Q_SLOTS:
32
33
33 void singleSurfaceStartsHidden();
34 void singleSurfaceStartsHidden();
34 void secondSurfaceIsHidden();
35 void secondSurfaceIsHidden();
36+ void rootFocus();
37+ void rootFocusInhibit();
35
38
36private:
39private:
37 ApplicationManager *applicationManager{nullptr};
40 ApplicationManager *applicationManager{nullptr};
@@ -125,6 +128,63 @@ void tst_TopLevelWindowModel::secondSurfaceIsHidden()
125 QCOMPARE((void*)topLevelWindowModel->windowAt(0)->surface(), (void*)firstSurface);
128 QCOMPARE((void*)topLevelWindowModel->windowAt(0)->surface(), (void*)firstSurface);
126}
129}
127
130
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+
128QTEST_MAIN(tst_TopLevelWindowModel)
188QTEST_MAIN(tst_TopLevelWindowModel)
129
189
130#include "tst_TopLevelWindowModel.moc"
190#include "tst_TopLevelWindowModel.moc"
tests/qmltests/Stage/tst_PhoneStage.qml+2−2
@@ -507,7 +507,7 @@ Item {
507 waitForRendering(stage);
507 waitForRendering(stage);
508
508
509 // Give nullwindow focus
509 // Give nullwindow focus
510− topLevelSurfaceList.rootFocus(false);
510+ topLevelSurfaceList.rootFocus = false;
511 var webbrowserSurfaceId = topLevelSurfaceList.nextId;
511 var webbrowserSurfaceId = topLevelSurfaceList.nextId;
512 topLevelSurfaceList.pendingActivation();
512 topLevelSurfaceList.pendingActivation();
513 var webbrowserApp = ApplicationManager.startApplication("morph-browser");
513 var webbrowserApp = ApplicationManager.startApplication("morph-browser");
@@ -515,7 +515,7 @@ Item {
515 var webbrowserWindow = topLevelSurfaceList.windowAt(0);
515 var webbrowserWindow = topLevelSurfaceList.windowAt(0);
516
516
517 // Give focus back to stage, but we should NOT refocus prev window (dash in this case)
517 // Give focus back to stage, but we should NOT refocus prev window (dash in this case)
518− topLevelSurfaceList.rootFocus(true);
518+ topLevelSurfaceList.rootFocus = true;
519
519
520 // We should have given focus to webbrowser at this point, not dashWindow
520 // We should have given focus to webbrowser at this point, not dashWindow
521 tryCompare(topLevelSurfaceList, "focusedWindow", webbrowserWindow);
521 tryCompare(topLevelSurfaceList, "focusedWindow", webbrowserWindow);