on xenial help sign in
Let TopLevelWindowModel deside what app to refocus, if any

This let TopLevelWindowModel deside what app it should refocus after a the null window has been focused. It will only refocus the previous window if null focus still has focus. This makes sure we dont give the focus to another app while an app is starting, or other window has clamed the focus. Pending Activation will block refocus of previous focused window this is needed since a surface activation with miral is async and a activation of placeholder is sync. This causes a race condition between placeholder and prev window. This results in prev window gets focused as it will always be later then placeholder. This fixes: https://github.com/ubports/ubuntu-touch/issues/1170

commitc08b89c2e6a53a7c1156dda0d1ac7542b4fd5b93 authorMarius Gripsgard <marius@ubports.com> dateWed 24 Jul 2019, 19:49 (7 years ago) parent35e53ef
6 files changed+77−22
plugins/WindowManager/TopLevelWindowModel.cpp+33−9 plugins/WindowManager/TopLevelWindowModel.h+7−1 qml/Launcher/Launcher.qml+1−1 qml/Shell.qml+3−1 qml/Stage/Stage.qml+7−10 tests/qmltests/Stage/tst_PhoneStage.qml+26
plugins/WindowManager/TopLevelWindowModel.cpp+33−9
@@ -650,16 +650,19 @@ void TopLevelWindowModel::setFocusedWindow(Window *window)
650650 if (window != m_focusedWindow) {
651651 INFO_MSG << "(" << window << ")";
652652
653− Window* previousWindow = m_focusedWindow;
653+ m_previousWindow = m_focusedWindow;
654654
655655 m_focusedWindow = window;
656656 Q_EMIT focusedWindowChanged(m_focusedWindow);
657657
658− if (previousWindow && previousWindow->focused() && !previousWindow->surface()) {
658+ if (m_previousWindow && m_previousWindow->focused() && !m_previousWindow->surface()) {
659659 // do it ourselves. miral doesn't know about this window
660− previousWindow->setFocused(false);
660+ m_previousWindow->setFocused(false);
661661 }
662662 }
663+
664+ // Reset
665+ m_pendingActivation = false;
663666}
664667
665668unityapi::MirSurfaceInterface* TopLevelWindowModel::inputMethodSurface() const
@@ -727,12 +730,6 @@ void TopLevelWindowModel::activateTopMostWindowWithoutId(int forbiddenId)
727730 }
728731}
729732
730−void TopLevelWindowModel::activateNullWindow()
731−{
732− if (!m_nullWindow->focused())
733− m_nullWindow->activate();
734−}
735−
736733void TopLevelWindowModel::closeAllWindows()
737734{
738735 m_closingAllApps = true;
@@ -740,3 +737,30 @@ void TopLevelWindowModel::closeAllWindows()
740737 win.window->close();
741738 }
742739}
740+
741+void TopLevelWindowModel::rootFocus(bool focus)
742+{
743+ if (focus) {
744+ // Give focus back to previous focused window, only if null window is focused.
745+ // If null window is not focused, a different app had taken the focus and we
746+ // should repect that, or if a pendingActivation is going on.
747+ if (m_previousWindow && !m_previousWindow->focused() && !m_pendingActivation &&
748+ m_nullWindow == m_focusedWindow && m_previousWindow != m_nullWindow) {
749+ m_previousWindow->activate();
750+ }
751+ } else {
752+ if (!m_nullWindow->focused()) {
753+ m_nullWindow->activate();
754+ }
755+ }
756+}
757+
758+// Pending Activation will block refocus of previous focused window
759+// this is needed since surface activation with miral is async,
760+// and activation of placeholder is sync. This causes a race condition
761+// between placeholder and prev window. This results in prev window
762+// gets focused, as it will always be later than the placeholder.
763+void TopLevelWindowModel::pendingActivation()
764+{
765+ m_pendingActivation = true;
766+}
plugins/WindowManager/TopLevelWindowModel.h+7−1
@@ -167,13 +167,17 @@ public:
167167 /**
168168 * @brief Raises and focuses a window with no surface
169169 */
170− Q_INVOKABLE void activateNullWindow();
170+ Q_INVOKABLE void rootFocus(bool focus);
171171
172172 /**
173173 * @brief Closes all windows, emits closedAllWindows when done
174174 */
175175 Q_INVOKABLE void closeAllWindows();
176176
177+ /**
178+ * @brief Sets pending activation flag
179+ */
180+ Q_INVOKABLE void pendingActivation();
177181Q_SIGNALS:
178182 void countChanged();
179183 void inputMethodSurfaceChanged(unity::shell::application::MirSurfaceInterface* inputMethodSurface);
@@ -252,6 +256,8 @@ private:
252256 Window* m_inputMethodWindow{nullptr};
253257 Window* m_focusedWindow{nullptr};
254258 Window* m_nullWindow;
259+ Window* m_previousWindow{nullptr};
260+ bool m_pendingActivation;
255261
256262 int m_nextId{1};
257263 // Just something big enough that we don't risk running out of unused id numbers.
qml/Launcher/Launcher.qml+1−1
@@ -375,8 +375,8 @@ FocusScope {
375375 allowSlidingAnimation: !dragArea.dragging && !launcherDragArea.drag.active && panel.animate
376376
377377 onApplicationSelected: {
378− root.hide();
379378 root.launcherApplicationSelected(appId)
379+ root.hide();
380380 root.focus = false;
381381 }
382382
qml/Shell.qml+3−1
@@ -1,4 +1,4 @@
1−/*
1+/*
22 * Copyright (C) 2013-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
@@ -180,6 +180,8 @@ StyledItem {
180180 readonly property alias greeter: greeterLoader.item
181181
182182 function activateApplication(appId) {
183+ topLevelSurfaceList.pendingActivation();
184+
183185 // Either open the app in our own session, or -- if we're acting as a
184186 // greeter -- ask the user's session to open it for us.
185187 if (shell.mode === "greeter") {
qml/Stage/Stage.qml+7−10
@@ -93,17 +93,14 @@ FocusScope {
9393 // Stage must have focus before activating windows, including null
9494 if (interactive) {
9595 focus = true;
96−
97− // Activate focused app once stage is active again
98− // this makes sure the focused app have focus
99− if (priv.focusedAppDelegate) {
100− priv.focusedAppDelegate.window.activate();
101− }
102− } else {
103− // Activate null window once stage is not active this makes
104− // sure none of the apps have focus when stage is not active
105− topLevelSurfaceList.activateNullWindow();
10696 }
97+
98+ // This will:
99+ // - If interactive: Try to reactivate last focused application.
100+ // this will not happen if a pending activation is going on
101+ // - If not interactive: Activate nullWindow, this makes
102+ // sure none of the apps have focus when stage is not active
103+ topLevelSurfaceList.rootFocus(interactive);
107104 }
108105
109106 onAltTabPressedChanged: {
tests/qmltests/Stage/tst_PhoneStage.qml+26
@@ -500,6 +500,32 @@ Item {
500500 compare(webbrowserApp.surfaceList.count, 1);
501501 }
502502
503+ function test_givePlaceholderFocusOnStartup() {
504+ compare(topLevelSurfaceList.applicationAt(0).appId, "unity8-dash");
505+ var dashSurfaceId = topLevelSurfaceList.idAt(0);
506+ waitUntilAppSurfaceShowsUp(dashSurfaceId);
507+ waitForRendering(stage);
508+
509+ // Give nullwindow focus
510+ topLevelSurfaceList.rootFocus(false);
511+ var webbrowserSurfaceId = topLevelSurfaceList.nextId;
512+ topLevelSurfaceList.pendingActivation();
513+ var webbrowserApp = ApplicationManager.startApplication("morph-browser");
514+ compare(topLevelSurfaceList.applicationAt(0).appId, "morph-browser");
515+ var webbrowserWindow = topLevelSurfaceList.windowAt(0);
516+
517+ // Give focus back to stage, but we should NOT refocus prev window (dash in this case)
518+ topLevelSurfaceList.rootFocus(true);
519+
520+ // We should have given focus to webbrowser at this point, not dashWindow
521+ tryCompare(topLevelSurfaceList, "focusedWindow", webbrowserWindow);
522+
523+ waitUntilAppSurfaceShowsUp(webbrowserSurfaceId);
524+
525+ // Should still have webbrowser as focused app
526+ tryCompare(topLevelSurfaceList, "focusedWindow", webbrowserWindow);
527+ }
528+
503529 /*
504530 1 - user suspends an application (ie, focus a surface from a different application)
505531 2 - That suspended application gets killed, causing its surface to go zombie