on xenial help sign in
Merge pull request #177 from ubports/xenial_-_edge_-_refocusfixes

Let TopLevelWindowModel decide what app to refocus, if any

commitab735599ccc7aa3a151081f64f27f15d71c2b0bb authorMarius Gripsgard <marius@ubports.com> dateMon 16 Sep 2019, 23:10 (7 years ago) parent0b987cbc08b89c
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