Mir

Merge lp:~alan-griffiths/mir/move-miral-test-to-mir into lp:mir

Proposed by Alan Griffiths on 2017-08-23
Status: Merged
Approved by: Gerry Boland on 2017-08-30
Approved revision: 4254
Merged at revision: 4236
Proposed branch: lp:~alan-griffiths/mir/move-miral-test-to-mir
Merge into: lp:mir
Prerequisite: lp:~alan-griffiths/mir/move-miral-to-mir
Diff against target: 4933 lines (+4803/-1)
23 files modified
include/common/mir/input/mir_input_config.h (+3/-1)
tests/CMakeLists.txt (+2/-0)
tests/miral/CMakeLists.txt (+57/-0)
tests/miral/active_outputs.cpp (+205/-0)
tests/miral/active_window.cpp (+412/-0)
tests/miral/client_mediated_gestures.cpp (+302/-0)
tests/miral/display_reconfiguration.cpp (+87/-0)
tests/miral/drag_active_window.cpp (+153/-0)
tests/miral/drag_and_drop.cpp (+656/-0)
tests/miral/modify_window_state.cpp (+105/-0)
tests/miral/mru_window_list.cpp (+193/-0)
tests/miral/raise_tree.cpp (+85/-0)
tests/miral/runner.cpp (+49/-0)
tests/miral/select_active_window.cpp (+121/-0)
tests/miral/test_server.cpp (+198/-0)
tests/miral/test_server.h (+90/-0)
tests/miral/test_window_manager_tools.h (+197/-0)
tests/miral/window_id.cpp (+114/-0)
tests/miral/window_placement.cpp (+554/-0)
tests/miral/window_placement_anchors_to_parent.cpp (+208/-0)
tests/miral/window_placement_client_api.cpp (+141/-0)
tests/miral/window_properties.cpp (+164/-0)
tests/miral/workspaces.cpp (+707/-0)
To merge this branch: bzr merge lp:~alan-griffiths/mir/move-miral-test-to-mir
Reviewer Review Type Date Requested Status
Gerry Boland Approve on 2017-08-30
Brandon Schaefer (community) 2017-08-23 Approve on 2017-08-30
Mir CI Bot continuous-integration Approve on 2017-08-24
Review via email: mp+329464@code.launchpad.net

Commit message

Incorporate miral project into mir source tree - part 2 (miral-test)

Description of the change

Incorporate miral project into mir source tree

This is a second-cut:

1. The utility script for generating the libmiral symbols file hasn't been ported
2. There's no attempt to remove code obsoleted by MirAL
3. There's no reworking of the generated docs to include miral

To post a comment you must log in.
Mir CI Bot (mir-ci-bot) wrote :

FAILED: Continuous integration, rev:4249
https://mir-jenkins.ubuntu.com/job/mir-ci/3576/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/4899/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/5121
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=artful/5110
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial/5110
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=zesty/5110
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=artful/4938/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/4938/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=artful/4938/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial/4938/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/4938/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=artful/4938
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=artful/4938/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=zesty/4938
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=zesty/4938/artifact/output/*zip*/output.zip
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial/4938/console

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/3576/rebuild

review: Needs Fixing (continuous-integration)
Alan Griffiths (alan-griffiths) wrote :

18:05:38 15/33 Test #15: miral-test ........................................***Failed 0.00 sec
18:05:38 /<<BUILDDIR>>/mir-1.0.0+zesty5110bzr4249/obj-x86_64-linux-gnu/bin/miral-test.bin: error while loading shared libraries: libmiral.so.2: cannot open shared object file: No such file or directory

Well... at least that proves the test is there!

Alan Griffiths (alan-griffiths) wrote :

>
> 18:05:38 15/33 Test #15: miral-test
> ........................................***Failed 0.00 sec
> 18:05:38 /<<BUILDDIR>>/mir-1.0.0+zesty5110bzr4249/obj-x86_64-linux-gnu/bin
> /miral-test.bin: error while loading shared libraries: libmiral.so.2: cannot
> open shared object file: No such file or directory
>
> Well... at least that proves the test is there!

Doh! "miral-test.bin" not "miral-test"

4250. By Alan Griffiths on 2017-08-24

Avoid cmake being too clever

Mir CI Bot (mir-ci-bot) wrote :

FAILED: Continuous integration, rev:4250
https://mir-jenkins.ubuntu.com/job/mir-ci/3577/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/4900/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/5122
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=artful/5111
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial/5111
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=zesty/5111
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=artful/4939/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/4939/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=artful/4939/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial/4939/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/4939/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=artful/4939
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=artful/4939/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=zesty/4939
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=zesty/4939/artifact/output/*zip*/output.zip
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial/4939/console

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/3577/rebuild

review: Needs Fixing (continuous-integration)
4251. By Alan Griffiths on 2017-08-24

Ignore the --logging flag passed to mir tests

4252. By Alan Griffiths on 2017-08-24

merge :parent

Mir CI Bot (mir-ci-bot) wrote :

FAILED: Continuous integration, rev:4252
https://mir-jenkins.ubuntu.com/job/mir-ci/3578/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/4901/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/5123
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=artful/5112
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial/5112
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=zesty/5112
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=artful/4940/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/4940/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=artful/4940
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=artful/4940/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial/4940
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial/4940/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/4940
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/4940/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=artful/4940
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=artful/4940/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=zesty/4940
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=zesty/4940/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial/4940
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial/4940/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/3578/rebuild

review: Needs Fixing (continuous-integration)
4253. By Alan Griffiths on 2017-08-24

Pander to clang

4254. By Alan Griffiths on 2017-08-24

merge lp:~alan-griffiths/mir/be-nice-to-clang

Mir CI Bot (mir-ci-bot) wrote :

PASSED: Continuous integration, rev:4254
https://mir-jenkins.ubuntu.com/job/mir-ci/3580/
Executed test runs:
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-mir/4903
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/5125
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=artful/5114
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial/5114
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=zesty/5114
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=artful/4942
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=artful/4942/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/4942
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/4942/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=artful/4942
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=artful/4942/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial/4942
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial/4942/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/4942
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/4942/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=artful/4942
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=artful/4942/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=zesty/4942
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=zesty/4942/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial/4942
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial/4942/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/3580/rebuild

review: Approve (continuous-integration)
Brandon Schaefer (brandontschaefer) wrote :

Sounds reasonable, the other branch is ready to land. LGTM

review: Approve
Gerry Boland (gerboland) wrote :

Builds ok, tests ok.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'include/common/mir/input/mir_input_config.h'
2--- include/common/mir/input/mir_input_config.h 2017-07-28 17:00:43 +0000
3+++ include/common/mir/input/mir_input_config.h 2017-08-24 15:19:58 +0000
4@@ -75,7 +75,9 @@
5 std::unique_ptr<Implementation> impl;
6 };
7
8-class MirInputConfig
9+// We use "struct", not "class" for consistency with mirclient/mir_toolkit/client_types.h:395
10+// (To be nice to downstreams that use clang with its pointless warnings about this.)
11+struct MirInputConfig
12 {
13 public:
14 MirInputConfig();
15
16=== modified file 'tests/CMakeLists.txt'
17--- tests/CMakeLists.txt 2017-05-08 03:04:26 +0000
18+++ tests/CMakeLists.txt 2017-08-24 15:19:58 +0000
19@@ -70,6 +70,8 @@
20 add_subdirectory(privileged-tests/)
21 endif(MIR_BUILD_PRIVILEGED_TESTS)
22
23+add_subdirectory(miral)
24+
25 # Private test headers used by integration and unit tests
26 include_directories(
27 include
28
29=== added directory 'tests/miral'
30=== added file 'tests/miral/CMakeLists.txt'
31--- tests/miral/CMakeLists.txt 1970-01-01 00:00:00 +0000
32+++ tests/miral/CMakeLists.txt 2017-08-24 15:19:58 +0000
33@@ -0,0 +1,57 @@
34+# We can't tell which version of gtest we're building against and INSTANTIATE_TEST_CASE_P changed in
35+# a way that relies on a gcc extension to support backward-compatible code, So...
36+check_cxx_compiler_flag(-Wno-gnu-zero-variadic-macro-arguments MIRAL_COMPILE_WITH_W_NO_GNU_ZERO_VARIADIC_MACRO_ARGUMENTS)
37+check_cxx_compiler_flag(-Wno-pedantic MIRAL_COMPILE_WITH_W_NO_PEDANTIC)
38+if ("${CMAKE_CXX_COMPILER}" MATCHES ".*clang.*" AND MIRAL_COMPILE_WITH_W_NO_GNU_ZERO_VARIADIC_MACRO_ARGUMENTS)
39+ set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-gnu-zero-variadic-macro-arguments") # clang
40+elseif(MIRAL_COMPILE_WITH_W_NO_PEDANTIC)
41+ set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-pedantic") #gcc
42+endif()
43+
44+include_directories(
45+ ${PROJECT_SOURCE_DIR}/src/miral
46+ ${MIRTEST_INCLUDE_DIRS}
47+ ${GMOCK_INCLUDE_DIR}
48+ ${GTEST_INCLUDE_DIR}
49+)
50+
51+if(${CMAKE_COMPILER_IS_GNUCXX})
52+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -flto")
53+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto")
54+ set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -flto")
55+ set(CMAKE_AR "gcc-ar")
56+ set(CMAKE_NM "gcc-nm")
57+ set(CMAKE_RANLIB "gcc-ranlib")
58+endif()
59+
60+mir_add_wrapped_executable(miral-test NOINSTALL
61+ mru_window_list.cpp
62+ active_outputs.cpp
63+ window_id.cpp
64+ runner.cpp
65+ select_active_window.cpp
66+ window_placement.cpp
67+ window_placement_anchors_to_parent.cpp
68+ window_placement_client_api.cpp
69+ window_properties.cpp
70+ drag_active_window.cpp
71+ modify_window_state.cpp
72+ test_server.cpp test_server.h
73+ test_window_manager_tools.h
74+ display_reconfiguration.cpp
75+ active_window.cpp
76+ raise_tree.cpp
77+ workspaces.cpp
78+ drag_and_drop.cpp
79+ client_mediated_gestures.cpp
80+)
81+
82+target_link_libraries(miral-test
83+ ${GTEST_BOTH_LIBRARIES}
84+ ${GMOCK_LIBRARIES}
85+ miral
86+ miral-internal
87+ mir-test-assist
88+)
89+
90+add_test(miral-test ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/miral-test)
91
92=== added file 'tests/miral/active_outputs.cpp'
93--- tests/miral/active_outputs.cpp 1970-01-01 00:00:00 +0000
94+++ tests/miral/active_outputs.cpp 2017-08-24 15:19:58 +0000
95@@ -0,0 +1,205 @@
96+/*
97+ * Copyright © 2016 Canonical Ltd.
98+ *
99+ * This program is free software: you can redistribute it and/or modify it
100+ * under the terms of the GNU General Public License version 2 or 3 as
101+ * published by the Free Software Foundation.
102+ *
103+ * This program is distributed in the hope that it will be useful,
104+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
105+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
106+ * GNU General Public License for more details.
107+ *
108+ * You should have received a copy of the GNU General Public License
109+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
110+ *
111+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
112+ */
113+
114+#include "miral/active_outputs.h"
115+#include "miral/output.h"
116+
117+#include <mir/shell/display_configuration_controller.h>
118+
119+#include <mir_test_framework/headless_test.h>
120+
121+#include <mir/test/doubles/fake_display.h>
122+#include <mir/test/doubles/stub_display_configuration.h>
123+#include <mir/test/fake_shared.h>
124+#include <mir/test/signal.h>
125+
126+#include <gtest/gtest.h>
127+#include <gmock/gmock.h>
128+
129+namespace mg = mir::graphics;
130+namespace mt = mir::test;
131+namespace mtd = mir::test::doubles;
132+namespace mtf = mir_test_framework;
133+
134+using namespace miral;
135+using namespace testing;
136+
137+namespace
138+{
139+struct MockActiveOutputsListener : ActiveOutputsListener
140+{
141+ MOCK_METHOD0(advise_output_begin, void());
142+ MOCK_METHOD0(advise_output_end, void());
143+
144+ MOCK_METHOD1(advise_output_create, void(Output const&));
145+ MOCK_METHOD2(advise_output_update, void(Output const&, Output const&));
146+ MOCK_METHOD1(advise_output_delete, void(Output const&));
147+};
148+
149+std::vector<Rectangle> const output_rects{
150+ {{0,0}, {640,480}},
151+ {{640,0}, {640,480}}
152+};
153+
154+struct ActiveOutputs : mtf::HeadlessTest
155+{
156+ ActiveOutputs()
157+ {
158+ add_to_environment("MIR_SERVER_NO_FILE", "");
159+ }
160+
161+ void SetUp() override
162+ {
163+ mtf::HeadlessTest::SetUp();
164+ preset_display(mt::fake_shared(display));
165+ active_outputs_monitor(server);
166+ active_outputs_monitor.add_listener(&active_outputs_listener);
167+ }
168+
169+ void TearDown() override
170+ {
171+ active_outputs_monitor.delete_listener(&active_outputs_listener);
172+ mtf::HeadlessTest::TearDown();
173+ }
174+
175+ mtd::FakeDisplay display{output_rects};
176+ ActiveOutputsMonitor active_outputs_monitor;
177+ NiceMock<MockActiveOutputsListener> active_outputs_listener;
178+
179+ void update_outputs(std::vector<Rectangle> const& displays)
180+ {
181+ mt::Signal signal;
182+ EXPECT_CALL(active_outputs_listener, advise_output_end()).WillOnce(Invoke([&]{signal.raise(); }));
183+
184+ mtd::StubDisplayConfig changed_stub_display_config{displays};
185+ display.emit_configuration_change_event(mt::fake_shared(changed_stub_display_config));
186+
187+ signal.wait_for(std::chrono::seconds(10));
188+ ASSERT_TRUE(signal.raised());
189+ }
190+
191+ void invert_outputs_in_base_configuration()
192+ {
193+ mt::Signal signal;
194+ EXPECT_CALL(active_outputs_listener, advise_output_end()).WillOnce(Invoke([&]{signal.raise(); }));
195+
196+ auto configuration = server.the_display()->configuration();
197+ configuration->for_each_output([](mg::UserDisplayConfigurationOutput& output)
198+ {
199+ output.orientation = mir_orientation_inverted;
200+ });
201+
202+ server.the_display_configuration_controller()->set_base_configuration(std::move(configuration));
203+
204+ signal.wait_for(std::chrono::seconds(10));
205+ ASSERT_TRUE(signal.raised());
206+ }
207+};
208+
209+struct RunServer
210+{
211+ RunServer(mtf::HeadlessTest* self) : self{self} { self->start_server(); }
212+ ~RunServer() { self->stop_server(); }
213+
214+ mtf::HeadlessTest* const self;
215+};
216+}
217+
218+TEST_F(ActiveOutputs, on_startup_listener_is_advised)
219+{
220+ InSequence seq;
221+ EXPECT_CALL(active_outputs_listener, advise_output_begin());
222+ EXPECT_CALL(active_outputs_listener, advise_output_create(_)).Times(2);
223+ RunServer runner{this};
224+
225+ Mock::VerifyAndClearExpectations(&active_outputs_listener); // before shutdown
226+}
227+
228+TEST_F(ActiveOutputs, when_output_unplugged_listener_is_advised)
229+{
230+ RunServer runner{this};
231+
232+ InSequence seq;
233+ EXPECT_CALL(active_outputs_listener, advise_output_begin());
234+ EXPECT_CALL(active_outputs_listener, advise_output_delete(_)).Times(1);
235+ update_outputs({{{0,0}, {640,480}}});
236+
237+ Mock::VerifyAndClearExpectations(&active_outputs_listener); // before shutdown
238+}
239+
240+TEST_F(ActiveOutputs, when_output_added_listener_is_advised)
241+{
242+ RunServer runner{this};
243+
244+ auto new_output_rects = output_rects;
245+ new_output_rects.emplace_back(Point{1280,0}, Size{640,480});
246+
247+ InSequence seq;
248+ EXPECT_CALL(active_outputs_listener, advise_output_begin());
249+ EXPECT_CALL(active_outputs_listener, advise_output_create(_)).Times(1);
250+ update_outputs(new_output_rects);
251+
252+ Mock::VerifyAndClearExpectations(&active_outputs_listener); // before shutdown
253+}
254+
255+TEST_F(ActiveOutputs, when_output_resized_listener_is_advised)
256+{
257+ RunServer runner{this};
258+
259+ auto new_output_rects = output_rects;
260+ new_output_rects[1] = {Point{640,0}, Size{1080,768}};
261+
262+ InSequence seq;
263+ EXPECT_CALL(active_outputs_listener, advise_output_begin());
264+ EXPECT_CALL(active_outputs_listener, advise_output_update(_, _)).Times(1);
265+ update_outputs(new_output_rects);
266+
267+ Mock::VerifyAndClearExpectations(&active_outputs_listener); // before shutdown
268+}
269+
270+TEST_F(ActiveOutputs, when_base_configuration_is_updated_listener_is_advised)
271+{
272+ RunServer runner{this};
273+
274+ InSequence seq;
275+ EXPECT_CALL(active_outputs_listener, advise_output_begin());
276+ EXPECT_CALL(active_outputs_listener, advise_output_update(_, _)).Times(2);
277+ invert_outputs_in_base_configuration();
278+
279+ Mock::VerifyAndClearExpectations(&active_outputs_listener); // before shutdown
280+}
281+
282+TEST_F(ActiveOutputs, available_to_process)
283+{
284+ RunServer runner{this};
285+
286+ active_outputs_monitor.process_outputs([](std::vector<Output> const& outputs)
287+ { EXPECT_THAT(outputs.size(), Eq(output_rects.size())); });
288+}
289+
290+TEST_F(ActiveOutputs, updates_are_available_to_process)
291+{
292+ RunServer runner{this};
293+
294+ auto new_output_rects = output_rects;
295+ new_output_rects.emplace_back(Point{1280,0}, Size{640,480});
296+ update_outputs(new_output_rects);
297+
298+ active_outputs_monitor.process_outputs([&](std::vector<Output> const& outputs)
299+ { EXPECT_THAT(outputs.size(), Eq(new_output_rects.size())); });
300+}
301
302=== added file 'tests/miral/active_window.cpp'
303--- tests/miral/active_window.cpp 1970-01-01 00:00:00 +0000
304+++ tests/miral/active_window.cpp 2017-08-24 15:19:58 +0000
305@@ -0,0 +1,412 @@
306+/*
307+ * Copyright © 2016 Canonical Ltd.
308+ *
309+ * This program is free software: you can redistribute it and/or modify it
310+ * under the terms of the GNU General Public License version 2 or 3 as
311+ * published by the Free Software Foundation.
312+ *
313+ * This program is distributed in the hope that it will be useful,
314+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
315+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
316+ * GNU General Public License for more details.
317+ *
318+ * You should have received a copy of the GNU General Public License
319+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
320+ *
321+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
322+ */
323+
324+#include "test_server.h"
325+
326+#include <mir/client/surface.h>
327+#include <mir/client/window.h>
328+#include <mir/client/window_spec.h>
329+#include <mir_toolkit/mir_buffer_stream.h>
330+
331+#include <miral/application_info.h>
332+
333+#include <mir/test/signal.h>
334+
335+#include <gtest/gtest.h>
336+#include <gmock/gmock.h>
337+
338+using namespace testing;
339+using namespace mir::client;
340+using namespace std::chrono_literals;
341+using miral::WindowManagerTools;
342+
343+
344+namespace
345+{
346+class FocusChangeSync
347+{
348+public:
349+ void exec(std::function<void()> const& f)
350+ {
351+ signal.reset();
352+ f();
353+ signal.wait_for(100ms);
354+ }
355+
356+ static void raise_signal_on_focus_change(MirWindow* /*surface*/, MirEvent const* event, void* context)
357+ {
358+ if (mir_event_get_type(event) == mir_event_type_window &&
359+ mir_window_event_get_attribute(mir_event_get_window_event(event)) == mir_window_attrib_focus)
360+ {
361+ ((FocusChangeSync*)context)->signal.raise();
362+ }
363+ }
364+
365+ auto signal_raised() -> bool { return signal.raised(); }
366+
367+private:
368+ mir::test::Signal signal;
369+};
370+
371+struct TestWindow : Surface, Window
372+{
373+ using Surface::operator=;
374+ using Window::operator=;
375+};
376+
377+struct ActiveWindow : public miral::TestServer
378+{
379+ FocusChangeSync sync1;
380+ FocusChangeSync sync2;
381+
382+ void paint(Surface const& surface)
383+ {
384+ mir_buffer_stream_swap_buffers_sync(
385+ mir_render_surface_get_buffer_stream(surface, 50, 50, mir_pixel_format_argb_8888));
386+ }
387+
388+ auto create_window(Connection const& connection, char const* name, FocusChangeSync& sync) -> TestWindow
389+ {
390+ TestWindow result;
391+
392+ result = Surface{mir_connection_create_render_surface_sync(connection, 50, 50)};
393+
394+ auto const spec = WindowSpec::for_normal_window(connection, 50, 50)
395+ .set_event_handler(&FocusChangeSync::raise_signal_on_focus_change, &sync)
396+ .add_surface(result, 50, 50, 0, 0)
397+ .set_name(name);
398+
399+ result = Window{spec.create_window()};
400+
401+ sync.exec([&]{ paint(result); });
402+
403+ EXPECT_TRUE(sync.signal_raised());
404+
405+ return result;
406+ }
407+
408+ auto create_tip(Connection const& connection, char const* name, Window const& parent, FocusChangeSync& sync) -> TestWindow
409+ {
410+ TestWindow result;
411+ result = Surface{mir_connection_create_render_surface_sync(connection, 50, 50)};
412+
413+ MirRectangle aux_rect{10, 10, 10, 10};
414+ auto const spec = WindowSpec::for_tip(connection, 50, 50, parent, &aux_rect, mir_edge_attachment_any)
415+ .set_event_handler(&FocusChangeSync::raise_signal_on_focus_change, &sync)
416+ .add_surface(result, 50, 50, 0, 0)
417+ .set_name(name);
418+
419+ result = Window{spec.create_window()};
420+
421+ // Expect this to timeout: A tip should not receive focus
422+ sync.exec([&]{ paint(result); });
423+ EXPECT_FALSE(sync.signal_raised());
424+
425+ return result;
426+ }
427+
428+ auto create_dialog(Connection const& connection, char const* name, Window const& parent, FocusChangeSync& sync) -> TestWindow
429+ {
430+ TestWindow result;
431+ result = Surface{mir_connection_create_render_surface_sync(connection, 50, 50)};
432+
433+ auto const spec = WindowSpec::for_dialog(connection, 50, 50, parent)
434+ .set_event_handler(&FocusChangeSync::raise_signal_on_focus_change, &sync)
435+ .add_surface(result, 50, 50, 0, 0)
436+ .set_name(name);
437+
438+ result = Window{spec.create_window()};
439+
440+ sync.exec([&]{ paint(result); });
441+ EXPECT_TRUE(sync.signal_raised());
442+
443+ return result;
444+ }
445+
446+ void assert_no_active_window()
447+ {
448+ invoke_tools([&](WindowManagerTools& tools)
449+ {
450+ auto const window = tools.active_window();
451+ ASSERT_FALSE(window);
452+ });
453+ }
454+
455+ void assert_active_window_is(char const* const name)
456+ {
457+ invoke_tools([&](WindowManagerTools& tools)
458+ {
459+ auto const window = tools.active_window();
460+ ASSERT_TRUE(window);
461+ auto const& window_info = tools.info_for(window);
462+ ASSERT_THAT(window_info.name(), Eq(name));
463+ });
464+ }
465+};
466+
467+auto const another_name = "second";
468+}
469+
470+TEST_F(ActiveWindow, a_single_window_when_ready_becomes_active)
471+{
472+ char const* const test_name = __PRETTY_FUNCTION__;
473+ auto const connection = connect_client(test_name);
474+
475+ auto const window = create_window(connection, test_name, sync1);
476+
477+ assert_active_window_is(test_name);
478+}
479+
480+TEST_F(ActiveWindow, a_single_window_when_hiding_becomes_inactive)
481+{
482+ char const* const test_name = __PRETTY_FUNCTION__;
483+ auto const connection = connect_client(test_name);
484+ auto const window = create_window(connection, test_name, sync1);
485+
486+ sync1.exec([&]{ mir_window_set_state(window, mir_window_state_hidden); });
487+
488+ EXPECT_TRUE(sync1.signal_raised());
489+ assert_no_active_window();
490+}
491+
492+TEST_F(ActiveWindow, a_single_window_when_unhiding_becomes_active)
493+{
494+ char const* const test_name = __PRETTY_FUNCTION__;
495+ auto const connection = connect_client(test_name);
496+ auto const window = create_window(connection, test_name, sync1);
497+
498+ sync1.exec([&]{ mir_window_set_state(window, mir_window_state_hidden); });
499+
500+ sync1.exec([&]{ mir_window_set_state(window, mir_window_state_restored); });
501+
502+ EXPECT_TRUE(sync1.signal_raised());
503+
504+ assert_active_window_is(test_name);
505+}
506+
507+TEST_F(ActiveWindow, a_second_window_when_ready_becomes_active)
508+{
509+ char const* const test_name = __PRETTY_FUNCTION__;
510+ auto const connection = connect_client(test_name);
511+
512+ auto const first_window = create_window(connection, "first", sync1);
513+ auto const window = create_window(connection, test_name, sync2);
514+
515+ assert_active_window_is(test_name);
516+}
517+
518+TEST_F(ActiveWindow, a_second_window_hiding_makes_first_active)
519+{
520+ char const* const test_name = __PRETTY_FUNCTION__;
521+ auto const connection = connect_client(test_name);
522+
523+ auto const first_window = create_window(connection, test_name, sync1);
524+ auto const window = create_window(connection, another_name, sync2);
525+
526+ sync2.exec([&]{ mir_window_set_state(window, mir_window_state_hidden); });
527+
528+ EXPECT_TRUE(sync2.signal_raised());
529+ assert_active_window_is(test_name);
530+}
531+
532+TEST_F(ActiveWindow, a_second_window_unhiding_leaves_first_active)
533+{
534+ char const* const test_name = __PRETTY_FUNCTION__;
535+ auto const connection = connect_client(test_name);
536+
537+ auto const first_window = create_window(connection, test_name, sync1);
538+ auto const window = create_window(connection, another_name, sync2);
539+
540+ sync1.exec([&]{ mir_window_set_state(window, mir_window_state_hidden); });
541+
542+ // Expect this to timeout
543+ sync2.exec([&]{ mir_window_set_state(window, mir_window_state_restored); });
544+
545+ EXPECT_THAT(sync2.signal_raised(), Eq(false));
546+ assert_active_window_is(test_name);
547+}
548+
549+TEST_F(ActiveWindow, switching_from_a_second_window_makes_first_active)
550+{
551+ char const* const test_name = __PRETTY_FUNCTION__;
552+ auto const connection = connect_client(test_name);
553+
554+ auto const first_window = create_window(connection, test_name, sync1);
555+ auto const window = create_window(connection, another_name, sync2);
556+
557+ sync1.exec([&]{ invoke_tools([](WindowManagerTools& tools){ tools.focus_next_within_application(); }); });
558+
559+ EXPECT_TRUE(sync1.signal_raised());
560+ assert_active_window_is(test_name);
561+}
562+
563+TEST_F(ActiveWindow, switching_from_a_second_application_makes_first_active)
564+{
565+ char const* const test_name = __PRETTY_FUNCTION__;
566+ auto const connection = connect_client(test_name);
567+ auto const second_connection = connect_client(another_name);
568+
569+ auto const first_window = create_window(connection, test_name, sync1);
570+ auto const window = create_window(second_connection, another_name, sync2);
571+
572+ sync1.exec([&]{ invoke_tools([](WindowManagerTools& tools){ tools.focus_next_application(); }); });
573+
574+ EXPECT_TRUE(sync1.signal_raised());
575+ assert_active_window_is(test_name);
576+}
577+
578+TEST_F(ActiveWindow, closing_a_second_application_makes_first_active)
579+{
580+ char const* const test_name = __PRETTY_FUNCTION__;
581+ auto const connection = connect_client(test_name);
582+
583+ auto const first_window = create_window(connection, test_name, sync1);
584+
585+ sync1.exec([&]
586+ {
587+ auto const second_connection = connect_client(another_name);
588+ auto const window = create_window(second_connection, another_name, sync2);
589+ assert_active_window_is(another_name);
590+ });
591+
592+ EXPECT_TRUE(sync1.signal_raised());
593+ assert_active_window_is(test_name);
594+}
595+
596+TEST_F(ActiveWindow, selecting_a_tip_makes_parent_active)
597+{
598+ char const* const test_name = __PRETTY_FUNCTION__;
599+ auto const connection = connect_client(test_name);
600+
601+ auto const parent = create_window(connection, test_name, sync1);
602+
603+ miral::Window parent_window;
604+ invoke_tools([&](WindowManagerTools& tools){ parent_window = tools.active_window(); });
605+
606+ // Steal the focus
607+ auto second_connection = connect_client(another_name);
608+ auto second_surface = create_window(second_connection, another_name, sync2);
609+
610+ auto const tip = create_tip(connection, "tip", parent, sync2);
611+
612+ sync1.exec([&]
613+ {
614+ invoke_tools([&](WindowManagerTools& tools)
615+ { tools.select_active_window(*tools.info_for(parent_window).children().begin()); });
616+ });
617+ EXPECT_TRUE(sync1.signal_raised());
618+
619+ assert_active_window_is(test_name);
620+}
621+
622+TEST_F(ActiveWindow, selecting_a_parent_makes_dialog_active)
623+{
624+ char const* const test_name = __PRETTY_FUNCTION__;
625+ auto const dialog_name = "dialog";
626+ auto const connection = connect_client(test_name);
627+
628+ auto const parent = create_window(connection, test_name, sync1);
629+
630+ miral::Window parent_window;
631+ invoke_tools([&](WindowManagerTools& tools){ parent_window = tools.active_window(); });
632+
633+ auto const dialog = create_dialog(connection, dialog_name, parent, sync2);
634+
635+ // Steal the focus
636+ auto second_connection = connect_client(another_name);
637+ auto second_surface = create_window(second_connection, another_name, sync1);
638+
639+ sync2.exec([&]{ invoke_tools([&](WindowManagerTools& tools){ tools.select_active_window(parent_window); }); });
640+
641+ EXPECT_TRUE(sync2.signal_raised());
642+ assert_active_window_is(dialog_name);
643+}
644+
645+TEST_F(ActiveWindow, input_methods_are_not_focussed)
646+{
647+ char const* const test_name = __PRETTY_FUNCTION__;
648+ auto const connection = connect_client(test_name);
649+
650+ auto const parent = create_window(connection, test_name, sync1);
651+ auto const input_method = WindowSpec::for_input_method(connection, 50, 50, parent).create_window();
652+
653+ assert_active_window_is(test_name);
654+
655+ invoke_tools([&](WindowManagerTools& tools)
656+ {
657+ auto const& info = tools.info_for(tools.active_window());
658+ tools.select_active_window(info.children().at(0));
659+ });
660+
661+ assert_active_window_is(test_name);
662+}
663+
664+TEST_F(ActiveWindow, satellites_are_not_focussed)
665+{
666+ char const* const test_name = __PRETTY_FUNCTION__;
667+ auto const connection = connect_client(test_name);
668+
669+ auto const parent = create_window(connection, test_name, sync1);
670+ auto const satellite = WindowSpec::for_satellite(connection, 50, 50, parent).create_window();
671+
672+ assert_active_window_is(test_name);
673+
674+ invoke_tools([&](WindowManagerTools& tools)
675+ {
676+ auto const& info = tools.info_for(tools.active_window());
677+ tools.select_active_window(info.children().at(0));
678+ });
679+
680+ assert_active_window_is(test_name);
681+}
682+
683+// lp:1671072
684+TEST_F(ActiveWindow, hiding_active_dialog_makes_parent_active)
685+{
686+ char const* const parent_name = __PRETTY_FUNCTION__;
687+ auto const dialog_name = "dialog";
688+ auto const connection = connect_client(parent_name);
689+
690+ auto const parent = create_window(connection, parent_name, sync1);
691+ auto const dialog = create_dialog(connection, dialog_name, parent, sync2);
692+
693+ sync1.exec([&]{ mir_window_set_state(dialog, mir_window_state_hidden); });
694+
695+ EXPECT_TRUE(sync1.signal_raised());
696+
697+ assert_active_window_is(parent_name);
698+}
699+
700+TEST_F(ActiveWindow, when_another_window_is_about_hiding_active_dialog_makes_parent_active)
701+{
702+ FocusChangeSync sync3;
703+ char const* const parent_name = __PRETTY_FUNCTION__;
704+ auto const dialog_name = "dialog";
705+ auto const another_window_name = "another window";
706+ auto const connection = connect_client(parent_name);
707+
708+ auto const parent = create_window(connection, parent_name, sync1);
709+ auto const another_window = create_window(connection, another_window_name, sync2);
710+ auto const dialog = create_dialog(connection, dialog_name, parent, sync3);
711+
712+ sync1.exec([&]{ mir_window_set_state(dialog, mir_window_state_hidden); });
713+
714+ EXPECT_TRUE(sync1.signal_raised());
715+
716+ assert_active_window_is(parent_name);
717+}
718
719=== added file 'tests/miral/client_mediated_gestures.cpp'
720--- tests/miral/client_mediated_gestures.cpp 1970-01-01 00:00:00 +0000
721+++ tests/miral/client_mediated_gestures.cpp 2017-08-24 15:19:58 +0000
722@@ -0,0 +1,302 @@
723+/*
724+ * Copyright © 2017 Canonical Ltd.
725+ *
726+ * This program is free software: you can redistribute it and/or modify it
727+ * under the terms of the GNU General Public License version 2 or 3 as
728+ * published by the Free Software Foundation.
729+ *
730+ * This program is distributed in the hope that it will be useful,
731+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
732+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
733+ * GNU General Public License for more details.
734+ *
735+ * You should have received a copy of the GNU General Public License
736+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
737+ *
738+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
739+ */
740+
741+#include <mir/client/surface.h>
742+#include <mir/client/window_spec.h>
743+
744+#include <mir_toolkit/mir_window.h>
745+#include <mir_toolkit/mir_blob.h>
746+
747+#include <mir/geometry/displacement.h>
748+#include <mir/input/input_device_info.h>
749+#include <mir/input/device_capability.h>
750+#include <mir/shell/canonical_window_manager.h>
751+#include <mir/shell/shell.h>
752+
753+#include <mir_test_framework/connected_client_with_a_window.h>
754+#include <mir_test_framework/fake_input_device.h>
755+#include <mir_test_framework/stub_server_platform_factory.h>
756+#include <mir/test/event_factory.h>
757+#include <mir/test/fake_shared.h>
758+#include <mir/test/signal.h>
759+
760+#include <gmock/gmock.h>
761+#include <gtest/gtest.h>
762+
763+#include <linux/input.h>
764+
765+#include <atomic>
766+
767+using namespace std::chrono_literals;
768+using namespace mir::geometry;
769+using namespace testing;
770+using mir::test::fake_shared;
771+using mir::test::Signal;
772+
773+namespace
774+{
775+class Cookie
776+{
777+public:
778+ Cookie() = default;
779+
780+ explicit Cookie(MirCookie const* cookie) : self{cookie, deleter} {}
781+
782+ operator MirCookie const*() const { return self.get(); }
783+
784+ auto get() const -> MirCookie const* { return self.get(); }
785+
786+ void reset() { self.reset(); }
787+
788+ void reset(MirCookie const* cookie) { self.reset(cookie, deleter); }
789+
790+private:
791+ static void deleter(MirCookie const* cookie) { mir_cookie_release(cookie); }
792+
793+ std::shared_ptr<MirCookie const> self;
794+};
795+
796+void mir_cookie_release(Cookie const&) = delete;
797+
798+struct MockWindowManager : mir::shell::CanonicalWindowManager
799+{
800+#if defined(__clang__)
801+ #pragma GCC diagnostic push
802+ #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
803+#endif
804+ using mir::shell::CanonicalWindowManager::CanonicalWindowManager;
805+#if defined(__clang__)
806+ #pragma GCC diagnostic pop
807+#endif
808+
809+ MOCK_METHOD3(handle_request_move,
810+ void(std::shared_ptr<mir::scene::Session> const&, std::shared_ptr<mir::scene::Surface> const&, uint64_t));
811+};
812+
813+struct MouseMoverAndFaker
814+{
815+ void start_dragging_mouse()
816+ {
817+ using namespace mir::input::synthesis;
818+ fake_mouse->emit_event(a_button_down_event().of_button(BTN_LEFT));
819+ }
820+
821+ void move_mouse(Displacement const& displacement)
822+ {
823+ using mir::input::synthesis::a_pointer_event;
824+ fake_mouse->emit_event(a_pointer_event().with_movement(displacement.dx.as_int(), displacement.dy.as_int()));
825+ }
826+
827+ void release_mouse()
828+ {
829+ using namespace mir::input::synthesis;
830+ fake_mouse->emit_event(a_button_up_event().of_button(BTN_LEFT));
831+ }
832+
833+private:
834+ std::unique_ptr<mir_test_framework::FakeInputDevice> fake_mouse{
835+ mir_test_framework::add_fake_input_device(
836+ mir::input::InputDeviceInfo{"mouse", "mouse-uid", mir::input::DeviceCapability::pointer})
837+ };
838+};
839+
840+Rectangle const screen_geometry{{0, 0}, {800, 600}};
841+auto const receive_event_timeout = 90s;
842+
843+struct ClientMediatedUserGestures : mir_test_framework::ConnectedClientWithAWindow,
844+ MouseMoverAndFaker
845+{
846+ void SetUp() override
847+ {
848+ initial_display_layout({screen_geometry});
849+ server.override_the_window_manager_builder([this](mir::shell::FocusController* focus_controller)
850+ {
851+ return window_manager =
852+ std::make_shared<MockWindowManager>(focus_controller, server.the_shell_display_layout());
853+ });
854+
855+ mir_test_framework::ConnectedClientWithAWindow::SetUp();
856+ mir_window_set_event_handler(window, &window_event_handler, this);
857+
858+ paint_window();
859+
860+ center_mouse();
861+ }
862+
863+ void TearDown() override
864+ {
865+ reset_window_event_handler();
866+ window_manager.reset();
867+ surface.reset();
868+ mir_test_framework::ConnectedClientWithAWindow::TearDown();
869+ }
870+
871+ auto user_initiates_gesture() -> Cookie;
872+
873+ std::shared_ptr<MockWindowManager> window_manager;
874+
875+private:
876+ void center_mouse();
877+ void paint_window();
878+ void set_window_event_handler(std::function<void(MirEvent const* event)> const& handler);
879+ void reset_window_event_handler();
880+ void invoke_window_event_handler(MirEvent const* event)
881+ {
882+ std::lock_guard<decltype(window_event_handler_mutex)> lock{window_event_handler_mutex};
883+ window_event_handler_(event);
884+ }
885+
886+ mir::client::Surface surface;
887+
888+ std::mutex window_event_handler_mutex;
889+ std::function<void(MirEvent const* event)> window_event_handler_ = [](MirEvent const*) {};
890+
891+ static void window_event_handler(MirWindow* window, MirEvent const* event, void* context);
892+};
893+
894+void ClientMediatedUserGestures::set_window_event_handler(std::function<void(MirEvent const* event)> const& handler)
895+{
896+ std::lock_guard<decltype(window_event_handler_mutex)> lock{window_event_handler_mutex};
897+ window_event_handler_ = handler;
898+}
899+
900+void ClientMediatedUserGestures::reset_window_event_handler()
901+{
902+ std::lock_guard<decltype(window_event_handler_mutex)> lock{window_event_handler_mutex};
903+ window_event_handler_ = [](MirEvent const*) {};
904+}
905+
906+void ClientMediatedUserGestures::window_event_handler(MirWindow* /*window*/, MirEvent const* event, void* context)
907+{
908+ static_cast<ClientMediatedUserGestures*>(context)->invoke_window_event_handler(event);
909+}
910+
911+void ClientMediatedUserGestures::paint_window()
912+{
913+ {
914+ surface = mir::client::Surface{mir_connection_create_render_surface_sync(connection, 42, 42)};
915+ auto const spec = mir::client::WindowSpec::for_changes(connection);
916+ mir_window_spec_add_render_surface(spec, surface, 42, 42, 0, 0);
917+ mir_window_apply_spec(window, spec);
918+ }
919+
920+ Signal have_focus;
921+
922+ set_window_event_handler([&](MirEvent const* event)
923+ {
924+ if (mir_event_get_type(event) != mir_event_type_window)
925+ return;
926+
927+ auto const window_event = mir_event_get_window_event(event);
928+ if (mir_window_event_get_attribute(window_event) != mir_window_attrib_focus)
929+ return;
930+
931+ if (mir_window_event_get_attribute_value(window_event))
932+ have_focus.raise();
933+ });
934+
935+ mir_buffer_stream_swap_buffers_sync(mir_render_surface_get_buffer_stream(surface, 42, 42, mir_pixel_format_argb_8888));
936+
937+ EXPECT_THAT(have_focus.wait_for(receive_event_timeout), Eq(true));
938+
939+ reset_window_event_handler();
940+}
941+
942+void ClientMediatedUserGestures::center_mouse()
943+{
944+ Signal have_mouseover;
945+
946+ set_window_event_handler([&](MirEvent const* event)
947+ {
948+ if (mir_event_get_type(event) != mir_event_type_input)
949+ return;
950+
951+ auto const input_event = mir_event_get_input_event(event);
952+
953+ if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer)
954+ return;
955+
956+ auto const pointer_event = mir_input_event_get_pointer_event(input_event);
957+
958+ if (mir_pointer_event_action(pointer_event) != mir_pointer_action_enter)
959+ return;
960+
961+ have_mouseover.raise();
962+ });
963+
964+ move_mouse(0.5 * as_displacement(screen_geometry.size));
965+
966+// We miss the "mouseover" occasionally (with valgrind and heavy stress about 1/20).
967+// But it isn't essential for the test and we've probably waited long enough
968+// for the mouse-down needed by the test to reach the window.
969+// EXPECT_THAT(have_mouseover.wait_for(receive_event_timeout), Eq(true));
970+ have_mouseover.wait_for(receive_event_timeout);
971+
972+ reset_window_event_handler();
973+}
974+
975+auto ClientMediatedUserGestures::user_initiates_gesture() -> Cookie
976+{
977+ Cookie cookie;
978+ Signal have_cookie;
979+
980+ set_window_event_handler([&](MirEvent const* event)
981+ {
982+ if (mir_event_get_type(event) != mir_event_type_input)
983+ return;
984+
985+ auto const input_event = mir_event_get_input_event(event);
986+
987+ if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer)
988+ return;
989+
990+ auto const pointer_event = mir_input_event_get_pointer_event(input_event);
991+
992+ if (mir_pointer_event_action(pointer_event) != mir_pointer_action_button_down)
993+ return;
994+
995+ cookie = Cookie{mir_input_event_get_cookie(input_event)};
996+ have_cookie.raise();
997+ });
998+
999+ start_dragging_mouse();
1000+
1001+ EXPECT_THAT(have_cookie.wait_for(receive_event_timeout), Eq(true));
1002+
1003+ reset_window_event_handler();
1004+ return cookie;
1005+}
1006+}
1007+
1008+TEST_F(ClientMediatedUserGestures, when_user_initiates_gesture_client_receives_cookie)
1009+{
1010+ auto const cookie = user_initiates_gesture();
1011+
1012+ EXPECT_THAT(cookie.get(), NotNull());
1013+}
1014+
1015+TEST_F(ClientMediatedUserGestures, when_client_initiates_move_window_manager_handles_request)
1016+{
1017+ auto const cookie = user_initiates_gesture();
1018+ Signal have_request;
1019+ EXPECT_CALL(*window_manager, handle_request_move(_, _, _)).WillOnce(InvokeWithoutArgs([&]{ have_request.raise(); }));
1020+
1021+ mir_window_request_user_move(window, cookie);
1022+
1023+ EXPECT_THAT(have_request.wait_for(receive_event_timeout), Eq(true));
1024+}
1025
1026=== added file 'tests/miral/display_reconfiguration.cpp'
1027--- tests/miral/display_reconfiguration.cpp 1970-01-01 00:00:00 +0000
1028+++ tests/miral/display_reconfiguration.cpp 2017-08-24 15:19:58 +0000
1029@@ -0,0 +1,87 @@
1030+/*
1031+ * Copyright © 2016 Canonical Ltd.
1032+ *
1033+ * This program is free software: you can redistribute it and/or modify it
1034+ * under the terms of the GNU General Public License version 2 or 3 as
1035+ * published by the Free Software Foundation.
1036+ *
1037+ * This program is distributed in the hope that it will be useful,
1038+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1039+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1040+ * GNU General Public License for more details.
1041+ *
1042+ * You should have received a copy of the GNU General Public License
1043+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1044+ *
1045+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
1046+ */
1047+
1048+#include "test_window_manager_tools.h"
1049+#include <mir/event_printer.h>
1050+
1051+using namespace miral;
1052+using namespace testing;
1053+namespace mt = mir::test;
1054+using mir::operator<<;
1055+
1056+namespace
1057+{
1058+X const display_left{0};
1059+Y const display_top{0};
1060+Width const display_width{640};
1061+Height const display_height{480};
1062+
1063+Rectangle const display_area{{display_left, display_top},
1064+ {display_width, display_height}};
1065+
1066+struct DisplayConfiguration : TestWindowManagerTools
1067+{
1068+ Size const initial_window_size{600, 400};
1069+
1070+ Window window;
1071+
1072+ void SetUp() override
1073+ {
1074+ basic_window_manager.add_display_for_testing(display_area);
1075+ basic_window_manager.add_session(session);
1076+ }
1077+
1078+ void create_fullscreen_window()
1079+ {
1080+ mir::scene::SurfaceCreationParameters creation_parameters;
1081+ creation_parameters.type = mir_window_type_normal;
1082+ creation_parameters.size = initial_window_size;
1083+ creation_parameters.state = mir_window_state_fullscreen;
1084+ creation_parameters.output_id = mir::graphics::DisplayConfigurationOutputId{0};
1085+
1086+ EXPECT_CALL(*window_manager_policy, advise_new_window(_))
1087+ .WillOnce(
1088+ Invoke(
1089+ [this](WindowInfo const& window_info)
1090+ { window = window_info.window(); }));
1091+
1092+ basic_window_manager.add_surface(session, creation_parameters, &create_surface);
1093+ basic_window_manager.select_active_window(window);
1094+
1095+ // Clear the expectations used to capture parent & child
1096+ Mock::VerifyAndClearExpectations(window_manager_policy);
1097+ }
1098+};
1099+}
1100+
1101+// This is the scenario behind lp:1640557
1102+TEST_F(DisplayConfiguration, given_fullscreen_windows_reconfiguring_displays_doesnt_crash)
1103+{
1104+ create_fullscreen_window();
1105+
1106+ WindowSpecification mods;
1107+ mods.state() = mir_window_state_hidden;
1108+ window_manager_tools.modify_window(window, mods);
1109+ mods.state() = mir_window_state_fullscreen;
1110+ window_manager_tools.modify_window(window, mods);
1111+
1112+ Rectangle const new_display{display_area.top_left+as_displacement({display_width, Height{0}}), display_area.size};
1113+
1114+ basic_window_manager.add_display_for_testing(new_display);
1115+ basic_window_manager.remove_display(new_display);
1116+}
1117
1118=== added file 'tests/miral/drag_active_window.cpp'
1119--- tests/miral/drag_active_window.cpp 1970-01-01 00:00:00 +0000
1120+++ tests/miral/drag_active_window.cpp 2017-08-24 15:19:58 +0000
1121@@ -0,0 +1,153 @@
1122+/*
1123+ * Copyright © 2016 Canonical Ltd.
1124+ *
1125+ * This program is free software: you can redistribute it and/or modify it
1126+ * under the terms of the GNU General Public License version 2 or 3 as
1127+ * published by the Free Software Foundation.
1128+ *
1129+ * This program is distributed in the hope that it will be useful,
1130+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1131+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1132+ * GNU General Public License for more details.
1133+ *
1134+ * You should have received a copy of the GNU General Public License
1135+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1136+ *
1137+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
1138+ */
1139+
1140+#include "test_window_manager_tools.h"
1141+#include <mir/event_printer.h>
1142+
1143+using namespace miral;
1144+using namespace testing;
1145+namespace mt = mir::test;
1146+using mir::operator<<;
1147+
1148+namespace
1149+{
1150+X const display_left{0};
1151+Y const display_top{0};
1152+Width const display_width{640};
1153+Height const display_height{480};
1154+
1155+Rectangle const display_area{{display_left, display_top},
1156+ {display_width, display_height}};
1157+
1158+struct DragActiveWindow : TestWindowManagerTools, WithParamInterface<MirWindowType>
1159+{
1160+ Size const initial_parent_size{600, 400};
1161+
1162+ Window window;
1163+
1164+ void SetUp() override
1165+ {
1166+ basic_window_manager.add_display_for_testing(display_area);
1167+ basic_window_manager.add_session(session);
1168+ }
1169+
1170+ void create_window_of_type(MirWindowType type)
1171+ {
1172+ mir::scene::SurfaceCreationParameters creation_parameters;
1173+ creation_parameters.type = type;
1174+ creation_parameters.size = initial_parent_size;
1175+
1176+ EXPECT_CALL(*window_manager_policy, advise_new_window(_))
1177+ .WillOnce(
1178+ Invoke(
1179+ [this](WindowInfo const& window_info)
1180+ { window = window_info.window(); }));
1181+
1182+ basic_window_manager.add_surface(session, creation_parameters, &create_surface);
1183+ basic_window_manager.select_active_window(window);
1184+
1185+ // Clear the expectations used to capture parent & child
1186+ Mock::VerifyAndClearExpectations(window_manager_policy);
1187+ }
1188+};
1189+
1190+using ForMoveableTypes = DragActiveWindow;
1191+using ForUnmoveableTypes = DragActiveWindow;
1192+
1193+TEST_P(ForMoveableTypes, moves)
1194+{
1195+ create_window_of_type(GetParam());
1196+
1197+ Displacement const movement{10, 10};
1198+ auto const initial_position = window.top_left();
1199+ auto const expected_position = initial_position + movement;
1200+
1201+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
1202+
1203+ window_manager_tools.drag_active_window(movement);
1204+
1205+ EXPECT_THAT(window.top_left(), Eq(expected_position))
1206+ << "Type: " << GetParam();
1207+}
1208+
1209+TEST_P(ForUnmoveableTypes, doesnt_move)
1210+{
1211+ create_window_of_type(GetParam());
1212+
1213+ Displacement const movement{10, 10};
1214+ auto const expected_position = window.top_left();
1215+
1216+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, _)).Times(0);
1217+
1218+ window_manager_tools.drag_active_window(movement);
1219+
1220+ EXPECT_THAT(window.top_left(), Eq(expected_position))
1221+ << "Type: " << GetParam();
1222+}
1223+}
1224+
1225+// When a surface is moved interactively
1226+// -------------------------------------
1227+// Regular, floating regular, dialog, and satellite surfaces should be user-movable.
1228+// Popups, glosses, and tips should not be.
1229+// Freestyle surfaces may or may not be, as specified by the app.
1230+// Mir and Unity: Surfaces, input, and displays (v0.3)
1231+INSTANTIATE_TEST_CASE_P(DragActiveWindow, ForMoveableTypes, ::testing::Values(
1232+ mir_window_type_normal,
1233+ mir_window_type_utility,
1234+ mir_window_type_dialog,
1235+// mir_window_type_gloss,
1236+ mir_window_type_freestyle
1237+// mir_window_type_menu,
1238+// mir_window_type_inputmethod,
1239+// mir_window_type_satellite,
1240+// mir_window_type_tip,
1241+// mir_window_types
1242+));
1243+
1244+
1245+INSTANTIATE_TEST_CASE_P(DragActiveWindow, ForUnmoveableTypes, ::testing::Values(
1246+// mir_window_type_normal,
1247+// mir_window_type_utility,
1248+// mir_window_type_dialog,
1249+ mir_window_type_gloss,
1250+// mir_window_type_freestyle,
1251+ mir_window_type_menu,
1252+ mir_window_type_inputmethod,
1253+// mir_window_type_satellite,
1254+ mir_window_type_tip
1255+// mir_window_types
1256+));
1257+
1258+using DragWindow = DragActiveWindow;
1259+
1260+TEST_F(DragWindow, can_drag_satellite)
1261+{
1262+ create_window_of_type(mir_window_type_satellite);
1263+
1264+ Displacement const movement{10, 10};
1265+ auto const initial_position = window.top_left();
1266+ auto const expected_position = initial_position + movement;
1267+
1268+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
1269+
1270+ window_manager_tools.drag_window(window, movement);
1271+
1272+ EXPECT_THAT(window.top_left(), Eq(expected_position))
1273+ << "Type: " << GetParam();
1274+}
1275\ No newline at end of file
1276
1277=== added file 'tests/miral/drag_and_drop.cpp'
1278--- tests/miral/drag_and_drop.cpp 1970-01-01 00:00:00 +0000
1279+++ tests/miral/drag_and_drop.cpp 2017-08-24 15:19:58 +0000
1280@@ -0,0 +1,656 @@
1281+/*
1282+ * Copyright © 2017 Canonical Ltd.
1283+ *
1284+ * This program is free software: you can redistribute it and/or modify it
1285+ * under the terms of the GNU General Public License version 2 or 3 as
1286+ * published by the Free Software Foundation.
1287+ *
1288+ * This program is distributed in the hope that it will be useful,
1289+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1290+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1291+ * GNU General Public License for more details.
1292+ *
1293+ * You should have received a copy of the GNU General Public License
1294+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1295+ *
1296+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
1297+ */
1298+
1299+#include <miral/window_management_policy_addendum2.h>
1300+
1301+#include <mir/client/blob.h>
1302+#include <mir/client/cookie.h>
1303+#include <mir/client/surface.h>
1304+#include <mir/client/window.h>
1305+#include <mir/client/window_spec.h>
1306+#include <mir_toolkit/mir_buffer_stream.h>
1307+#include <mir_toolkit/extensions/drag_and_drop.h>
1308+
1309+#include <mir/geometry/displacement.h>
1310+#include <mir/input/input_device_info.h>
1311+#include <mir/input/device_capability.h>
1312+#include <mir/shell/shell.h>
1313+
1314+#include "test_server.h"
1315+#include <mir_test_framework/fake_input_device.h>
1316+#include <mir_test_framework/stub_server_platform_factory.h>
1317+#include <mir/test/event_factory.h>
1318+#include <mir/test/signal.h>
1319+
1320+#include <gmock/gmock.h>
1321+#include <gtest/gtest.h>
1322+
1323+#include <linux/input.h>
1324+#include <uuid/uuid.h>
1325+
1326+#include <boost/throw_exception.hpp>
1327+#include <atomic>
1328+
1329+using namespace std::chrono_literals;
1330+using namespace mir::client;
1331+using namespace mir::geometry;
1332+using namespace testing;
1333+using mir::test::Signal;
1334+
1335+namespace
1336+{
1337+struct MouseMoverAndFaker
1338+{
1339+ void start_dragging_mouse()
1340+ {
1341+ using namespace mir::input::synthesis;
1342+ fake_mouse->emit_event(a_button_down_event().of_button(BTN_LEFT));
1343+ }
1344+
1345+ void move_mouse(Displacement const& displacement)
1346+ {
1347+ using mir::input::synthesis::a_pointer_event;
1348+ fake_mouse->emit_event(a_pointer_event().with_movement(displacement.dx.as_int(), displacement.dy.as_int()));
1349+ }
1350+
1351+ void release_mouse()
1352+ {
1353+ using namespace mir::input::synthesis;
1354+ fake_mouse->emit_event(a_button_up_event().of_button(BTN_LEFT));
1355+ }
1356+
1357+private:
1358+ std::unique_ptr<mir_test_framework::FakeInputDevice> fake_mouse{
1359+ mir_test_framework::add_fake_input_device(
1360+ mir::input::InputDeviceInfo{"mouse", "mouse-uid", mir::input::DeviceCapability::pointer})};
1361+};
1362+
1363+Rectangle const screen_geometry{{0,0}, {800,600}};
1364+auto const receive_event_timeout = 1s; //90s;
1365+
1366+struct ConnectedClientWithAWindow : miral::TestServer
1367+{
1368+ Connection connection;
1369+ Surface surface;
1370+ Window window;
1371+
1372+ void SetUp() override
1373+ {
1374+ miral::TestServer::SetUp();
1375+ connection = connect_client(__func__);
1376+ auto const width = surface_size.width.as_int();
1377+ auto const height = surface_size.height.as_int();
1378+ surface = Surface{mir_connection_create_render_surface_sync(connection, width, height)};
1379+ window = WindowSpec::for_normal_window(connection, width, height)
1380+ .set_name("ConnectedClientWithAWindow")
1381+ .add_surface(surface, width, height, 0, 0)
1382+ .create_window();
1383+ }
1384+
1385+ void TearDown() override
1386+ {
1387+ window.reset();
1388+ surface.reset();
1389+ connection.reset();
1390+ miral::TestServer::TearDown();
1391+ }
1392+
1393+ mir::geometry::Size const surface_size {640, 480};
1394+};
1395+
1396+struct DragAndDrop : ConnectedClientWithAWindow,
1397+ MouseMoverAndFaker
1398+{
1399+ MirDragAndDropV1 const* dnd = nullptr;
1400+
1401+ void SetUp() override
1402+ {
1403+ mir_test_framework::set_next_display_rects(std::unique_ptr<std::vector<Rectangle>>(new std::vector<Rectangle>({screen_geometry})));
1404+
1405+ ConnectedClientWithAWindow::SetUp();
1406+ dnd = mir_drag_and_drop_v1(connection);
1407+ mir_window_set_event_handler(window, &window_event_handler, this);
1408+ if (dnd) dnd->set_start_drag_and_drop_callback(window, &window_dnd_start_handler, this);
1409+
1410+ create_target_window();
1411+
1412+ paint_window(surface, window);
1413+
1414+ center_mouse();
1415+ }
1416+
1417+ void TearDown() override
1418+ {
1419+ reset_window_event_handler(target_window);
1420+ reset_window_event_handler(window);
1421+ target_window.reset();
1422+ target_surface.reset();
1423+ another_connection.reset();
1424+ ConnectedClientWithAWindow::TearDown();
1425+ }
1426+
1427+ auto user_initiates_drag() -> Cookie;
1428+ auto client_requests_drag(Cookie const& cookie) -> Blob;
1429+ auto handle_from_mouse_move() -> Blob;
1430+ auto handle_from_mouse_leave() -> Blob;
1431+ auto handle_from_mouse_enter() -> Blob;
1432+ auto handle_from_mouse_release() -> Blob;
1433+ auto count_of_handles_when_moving_mouse() -> int;
1434+
1435+private:
1436+ auto build_window_manager_policy(miral::WindowManagerTools const& tools) -> std::unique_ptr<TestWindowManagerPolicy> override;
1437+ void center_mouse();
1438+ void paint_window(MirRenderSurface* s, MirWindow* w);
1439+ void set_window_event_handler(MirWindow* window, std::function<void(MirEvent const* event)> const& handler);
1440+ void set_window_dnd_start_handler(MirWindow* window, std::function<void(MirDragAndDropEvent const*)> const& handler);
1441+ void reset_window_event_handler(MirWindow* window);
1442+
1443+ void create_target_window()
1444+ {
1445+ another_connection = connect_client("another_connection");
1446+ auto const height = screen_geometry.size.height.as_int();
1447+ auto const width = screen_geometry.size.width.as_int();
1448+ target_surface = Surface{mir_connection_create_render_surface_sync(another_connection, width,height)};
1449+ target_window = WindowSpec::for_normal_window(another_connection, width, height)
1450+ .set_name("target_window")
1451+ .add_surface(target_surface, width, height, 0, 0)
1452+ .set_event_handler(&window_event_handler, this)
1453+ .create_window();
1454+
1455+ paint_window(target_surface, target_window);
1456+ }
1457+
1458+ void invoke_window_event_handler(MirWindow* window, MirEvent const* event)
1459+ {
1460+ std::lock_guard<decltype(window_event_handler_mutex)> lock{window_event_handler_mutex};
1461+ if (window == this->window) window_event_handler_(event);
1462+ if (window == target_window) target_window_event_handler_(event);
1463+ }
1464+
1465+ void invoke_window_dnd_start_handler(MirWindow* window, MirDragAndDropEvent const* event)
1466+ {
1467+ std::lock_guard<decltype(window_event_handler_mutex)> lock{window_event_handler_mutex};
1468+ if (window == this->window) window_dnd_start_(event);
1469+ }
1470+
1471+ std::mutex window_event_handler_mutex;
1472+ std::function<void(MirDragAndDropEvent const* event)> window_dnd_start_ = [](MirDragAndDropEvent const*) {};
1473+ std::function<void(MirEvent const* event)> window_event_handler_ = [](MirEvent const*) {};
1474+ std::function<void(MirEvent const* event)> target_window_event_handler_ = [](MirEvent const*) {};
1475+
1476+ static void window_event_handler(MirWindow* window, MirEvent const* event, void* context);
1477+ static void window_dnd_start_handler(MirWindow* window, MirDragAndDropEvent const* event, void* context);
1478+
1479+ Connection another_connection;
1480+ Surface target_surface;
1481+ Window target_window;
1482+};
1483+
1484+void DragAndDrop::set_window_event_handler(MirWindow* window, std::function<void(MirEvent const* event)> const& handler)
1485+{
1486+ std::lock_guard<decltype(window_event_handler_mutex)> lock{window_event_handler_mutex};
1487+ if (window == this->window) window_event_handler_ = handler;
1488+ if (window == target_window) target_window_event_handler_ = handler;
1489+}
1490+
1491+void DragAndDrop::set_window_dnd_start_handler(MirWindow* window, std::function<void(MirDragAndDropEvent const*)> const& handler)
1492+{
1493+std::lock_guard<decltype(window_event_handler_mutex)> lock{window_event_handler_mutex};
1494+if (window == this->window) window_dnd_start_ = handler;
1495+}
1496+
1497+
1498+void DragAndDrop::reset_window_event_handler(MirWindow* window)
1499+{
1500+ if (window == this->window) window_event_handler_ = [](MirEvent const*) {};
1501+ if (window == target_window) target_window_event_handler_ = [](MirEvent const*) {};
1502+}
1503+
1504+void DragAndDrop::paint_window(MirRenderSurface* s, MirWindow* w)
1505+{
1506+ Signal have_focus;
1507+
1508+ set_window_event_handler(w, [&](MirEvent const* event)
1509+ {
1510+ if (mir_event_get_type(event) != mir_event_type_window)
1511+ return;
1512+
1513+ auto const window_event = mir_event_get_window_event(event);
1514+ if (mir_window_event_get_attribute(window_event) != mir_window_attrib_focus)
1515+ return;
1516+
1517+ if (mir_window_event_get_attribute_value(window_event))
1518+ have_focus.raise();
1519+ });
1520+
1521+ int width;
1522+ int height;
1523+ mir_render_surface_get_size(s, &width, &height);
1524+ mir_buffer_stream_swap_buffers_sync(
1525+ mir_render_surface_get_buffer_stream(s, width, height, mir_pixel_format_argb_8888));
1526+
1527+ EXPECT_THAT(have_focus.wait_for(receive_event_timeout), Eq(true));
1528+
1529+ reset_window_event_handler(w);
1530+}
1531+
1532+void DragAndDrop::center_mouse()
1533+{
1534+ Signal have_mouseover;
1535+
1536+ set_window_event_handler(window, [&](MirEvent const* event)
1537+ {
1538+ if (mir_event_get_type(event) != mir_event_type_input)
1539+ return;
1540+
1541+ auto const input_event = mir_event_get_input_event(event);
1542+
1543+ if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer)
1544+ return;
1545+
1546+ auto const pointer_event = mir_input_event_get_pointer_event(input_event);
1547+
1548+ if (mir_pointer_event_action(pointer_event) != mir_pointer_action_enter)
1549+ return;
1550+
1551+ have_mouseover.raise();
1552+ });
1553+
1554+ move_mouse(0.5 * as_displacement(screen_geometry.size));
1555+
1556+// We miss the "mouseover" occasionally (with valgrind and heavy stress about 1/20).
1557+// But it isn't essential for the test and we've probably waited long enough
1558+// for the mouse-down needed by the test to reach the window.
1559+// EXPECT_THAT(have_mouseover.wait_for(receive_event_timeout), Eq(true));
1560+ have_mouseover.wait_for(receive_event_timeout);
1561+
1562+ reset_window_event_handler(window);
1563+}
1564+
1565+void DragAndDrop::window_event_handler(MirWindow* window, MirEvent const* event, void* context)
1566+{
1567+ static_cast<DragAndDrop*>(context)->invoke_window_event_handler(window, event);
1568+}
1569+
1570+void DragAndDrop::window_dnd_start_handler(MirWindow* window, MirDragAndDropEvent const* event, void* context)
1571+{
1572+ static_cast<DragAndDrop*>(context)->invoke_window_dnd_start_handler(window, event);
1573+}
1574+
1575+
1576+auto DragAndDrop::user_initiates_drag() -> Cookie
1577+{
1578+ Cookie cookie;
1579+ Signal have_cookie;
1580+
1581+ set_window_event_handler(window, [&](MirEvent const* event)
1582+ {
1583+ if (mir_event_get_type(event) != mir_event_type_input)
1584+ return;
1585+
1586+ auto const input_event = mir_event_get_input_event(event);
1587+
1588+ if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer)
1589+ return;
1590+
1591+ auto const pointer_event = mir_input_event_get_pointer_event(input_event);
1592+
1593+ if (mir_pointer_event_action(pointer_event) != mir_pointer_action_button_down)
1594+ return;
1595+
1596+ cookie = Cookie{mir_input_event_get_cookie(input_event)};
1597+ have_cookie.raise();
1598+ });
1599+
1600+ start_dragging_mouse();
1601+
1602+ EXPECT_THAT(have_cookie.wait_for(receive_event_timeout), Eq(true));
1603+
1604+ reset_window_event_handler(window);
1605+ return cookie;
1606+}
1607+
1608+auto DragAndDrop::client_requests_drag(Cookie const& cookie) -> Blob
1609+{
1610+ Blob blob;
1611+ Signal initiated;
1612+
1613+ set_window_dnd_start_handler(window, [&](MirDragAndDropEvent const* event)
1614+ {
1615+ if (dnd)
1616+ blob.reset(dnd->start_drag_and_drop(event));
1617+
1618+ if (blob)
1619+ initiated.raise();
1620+ });
1621+
1622+ EXPECT_THAT(dnd, Ne(nullptr)) << "No Drag and Drop extension";
1623+
1624+ if (dnd)
1625+ dnd->request_drag_and_drop(window, cookie);
1626+
1627+ EXPECT_TRUE(initiated.wait_for(receive_event_timeout));
1628+
1629+ reset_window_event_handler(window);
1630+ return blob;
1631+}
1632+
1633+auto DragAndDrop::handle_from_mouse_move() -> Blob
1634+{
1635+ Blob blob;
1636+ Signal have_blob;
1637+
1638+ set_window_event_handler(window, [&](MirEvent const* event)
1639+ {
1640+ if (mir_event_get_type(event) != mir_event_type_input)
1641+ return;
1642+
1643+ auto const input_event = mir_event_get_input_event(event);
1644+
1645+ if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer)
1646+ return;
1647+
1648+ auto const pointer_event = mir_input_event_get_pointer_event(input_event);
1649+
1650+ EXPECT_THAT(dnd, Ne(nullptr)) << "No Drag and Drop extension";
1651+
1652+ if (dnd)
1653+ blob.reset(dnd->pointer_drag_and_drop(pointer_event));
1654+
1655+ if (blob)
1656+ have_blob.raise();
1657+ });
1658+
1659+ move_mouse({1,1});
1660+
1661+ EXPECT_TRUE(have_blob.wait_for(receive_event_timeout));
1662+
1663+ reset_window_event_handler(window);
1664+ return blob;
1665+}
1666+
1667+auto DragAndDrop::handle_from_mouse_leave() -> Blob
1668+{
1669+ Blob blob;
1670+ Signal have_blob;
1671+
1672+ set_window_event_handler(window, [&](MirEvent const* event)
1673+ {
1674+ if (mir_event_get_type(event) != mir_event_type_input)
1675+ return;
1676+
1677+ auto const input_event = mir_event_get_input_event(event);
1678+
1679+ if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer)
1680+ return;
1681+
1682+ auto const pointer_event = mir_input_event_get_pointer_event(input_event);
1683+
1684+ if (mir_pointer_event_action(pointer_event) != mir_pointer_action_leave)
1685+ return;
1686+
1687+ EXPECT_THAT(dnd, Ne(nullptr)) << "No Drag and Drop extension";
1688+
1689+ if (dnd)
1690+ blob.reset(dnd->pointer_drag_and_drop(pointer_event));
1691+
1692+ if (blob)
1693+ have_blob.raise();
1694+ });
1695+
1696+ move_mouse({1,1});
1697+ move_mouse(0.5 * as_displacement(surface_size));
1698+
1699+ EXPECT_TRUE(have_blob.wait_for(receive_event_timeout));
1700+
1701+ reset_window_event_handler(window);
1702+ return blob;
1703+}
1704+
1705+auto DragAndDrop::handle_from_mouse_enter() -> Blob
1706+{
1707+ Blob blob;
1708+ Signal have_blob;
1709+
1710+ set_window_event_handler(target_window, [&](MirEvent const* event)
1711+ {
1712+ if (mir_event_get_type(event) != mir_event_type_input)
1713+ return;
1714+
1715+ auto const input_event = mir_event_get_input_event(event);
1716+
1717+ if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer)
1718+ return;
1719+
1720+ auto const pointer_event = mir_input_event_get_pointer_event(input_event);
1721+
1722+ if (mir_pointer_event_action(pointer_event) != mir_pointer_action_enter)
1723+ return;
1724+
1725+ EXPECT_THAT(dnd, Ne(nullptr)) << "No Drag and Drop extension";
1726+
1727+ if (dnd)
1728+ blob.reset(dnd->pointer_drag_and_drop(pointer_event));
1729+
1730+ if (blob)
1731+ have_blob.raise();
1732+ });
1733+
1734+ move_mouse({1,1});
1735+ move_mouse(0.5 * as_displacement(surface_size));
1736+
1737+ EXPECT_TRUE(have_blob.wait_for(receive_event_timeout));
1738+
1739+ reset_window_event_handler(target_window);
1740+ return blob;
1741+}
1742+
1743+auto DragAndDrop::handle_from_mouse_release() -> Blob
1744+{
1745+ Blob blob;
1746+ Signal have_blob;
1747+
1748+ set_window_event_handler(target_window, [&](MirEvent const* event)
1749+ {
1750+ if (mir_event_get_type(event) != mir_event_type_input)
1751+ return;
1752+
1753+ auto const input_event = mir_event_get_input_event(event);
1754+
1755+ if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer)
1756+ return;
1757+
1758+ auto const pointer_event = mir_input_event_get_pointer_event(input_event);
1759+
1760+ if (mir_pointer_event_action(pointer_event) != mir_pointer_action_button_up)
1761+ return;
1762+
1763+ EXPECT_THAT(dnd, Ne(nullptr)) << "No Drag and Drop extension";
1764+
1765+ if (dnd)
1766+ blob.reset(dnd->pointer_drag_and_drop(pointer_event));
1767+
1768+ if (blob)
1769+ have_blob.raise();
1770+ });
1771+
1772+ move_mouse({1,1});
1773+ move_mouse(0.5 * as_displacement(surface_size));
1774+ release_mouse();
1775+
1776+ EXPECT_TRUE(have_blob.wait_for(receive_event_timeout));
1777+
1778+ reset_window_event_handler(target_window);
1779+ return blob;
1780+}
1781+
1782+auto DragAndDrop::count_of_handles_when_moving_mouse() -> int
1783+{
1784+ Signal have_3_events;
1785+ std::atomic<int> events{0};
1786+ std::atomic<int> handles{0};
1787+
1788+ auto counter = [&](MirEvent const* event)
1789+ {
1790+ if (mir_event_get_type(event) != mir_event_type_input)
1791+ return;
1792+
1793+ auto const input_event = mir_event_get_input_event(event);
1794+
1795+ if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer)
1796+ return;
1797+
1798+ auto const pointer_event = mir_input_event_get_pointer_event(input_event);
1799+
1800+ EXPECT_THAT(dnd, Ne(nullptr)) << "No Drag and Drop extension";
1801+
1802+ Blob blob;
1803+ if (dnd)
1804+ blob.reset(dnd->pointer_drag_and_drop(pointer_event));
1805+
1806+ if (blob)
1807+ handles.fetch_add(1);
1808+
1809+ if (events.fetch_add(1) == 2)
1810+ have_3_events.raise();
1811+ };
1812+
1813+ set_window_event_handler(window, counter);
1814+ set_window_event_handler(target_window, counter);
1815+
1816+ start_dragging_mouse();
1817+ move_mouse({1,1});
1818+ release_mouse();
1819+
1820+ EXPECT_TRUE(have_3_events.wait_for(receive_event_timeout));
1821+
1822+ reset_window_event_handler(window);
1823+ reset_window_event_handler(target_window);
1824+ return handles;
1825+}
1826+
1827+auto DragAndDrop::build_window_manager_policy(miral::WindowManagerTools const& tools) -> std::unique_ptr<TestWindowManagerPolicy>
1828+{
1829+ struct DnDWindowManagerPolicy : miral::TestServer::TestWindowManagerPolicy, miral::WindowManagementPolicyAddendum2
1830+ {
1831+ using miral::TestServer::TestWindowManagerPolicy::TestWindowManagerPolicy;
1832+
1833+ void handle_request_drag_and_drop(miral::WindowInfo& window_info) override
1834+ {
1835+ uuid_t uuid;
1836+ uuid_generate(uuid);
1837+ std::vector<uint8_t> const handle{std::begin(uuid), std::end(uuid)};
1838+
1839+ tools.start_drag_and_drop(window_info, handle);
1840+ }
1841+
1842+ void handle_request_move(miral::WindowInfo&, MirInputEvent const*) override {}
1843+ };
1844+
1845+ return std::make_unique<DnDWindowManagerPolicy>(tools, *this);
1846+}
1847+
1848+MATCHER_P(BlobContentEq, p, "")
1849+{
1850+ if (!arg || !p)
1851+ return false;
1852+ if (mir_blob_size(arg) != mir_blob_size(p))
1853+ return false;
1854+ return !memcmp(mir_blob_data(arg), mir_blob_data(p), mir_blob_size(p));
1855+}
1856+}
1857+
1858+TEST_F(DragAndDrop, when_user_initiates_drag_client_receives_cookie)
1859+{
1860+ auto const cookie = user_initiates_drag();
1861+
1862+ EXPECT_THAT(cookie, Ne(nullptr));
1863+}
1864+
1865+TEST_F(DragAndDrop, when_client_requests_drags_it_receives_handle)
1866+{
1867+ auto const cookie = user_initiates_drag();
1868+ ASSERT_THAT(cookie, Ne(nullptr));
1869+
1870+ auto const handle = client_requests_drag(cookie);
1871+
1872+ EXPECT_THAT(handle, Ne(nullptr));
1873+}
1874+
1875+TEST_F(DragAndDrop, during_drag_when_user_moves_mouse_client_receives_handle)
1876+{
1877+ auto const cookie = user_initiates_drag();
1878+ ASSERT_THAT(cookie, Ne(nullptr));
1879+ auto const handle_from_request = client_requests_drag(cookie);
1880+
1881+ auto const handle = handle_from_mouse_move();
1882+
1883+ EXPECT_THAT(handle, Ne(nullptr));
1884+ EXPECT_THAT(handle, BlobContentEq(handle_from_request));
1885+}
1886+
1887+TEST_F(DragAndDrop, when_drag_moves_from_window_leave_event_contains_handle)
1888+{
1889+ auto const cookie = user_initiates_drag();
1890+ ASSERT_THAT(cookie, Ne(nullptr));
1891+ auto const handle_from_request = client_requests_drag(cookie);
1892+
1893+ auto const handle = handle_from_mouse_leave();
1894+
1895+ EXPECT_THAT(handle, Ne(nullptr));
1896+ EXPECT_THAT(handle, BlobContentEq(handle_from_request));
1897+}
1898+
1899+TEST_F(DragAndDrop, when_drag_enters_target_window_enter_event_contains_handle)
1900+{
1901+ auto const cookie = user_initiates_drag();
1902+ ASSERT_THAT(cookie, Ne(nullptr));
1903+ auto const handle_from_request = client_requests_drag(cookie);
1904+
1905+ auto const handle = handle_from_mouse_enter();
1906+
1907+ EXPECT_THAT(handle, Ne(nullptr));
1908+ EXPECT_THAT(handle, BlobContentEq(handle_from_request));
1909+}
1910+
1911+TEST_F(DragAndDrop, when_drag_releases_target_window_release_event_contains_handle)
1912+{
1913+ auto const cookie = user_initiates_drag();
1914+ ASSERT_THAT(cookie, Ne(nullptr));
1915+ auto const handle_from_request = client_requests_drag(cookie);
1916+
1917+ auto const handle = handle_from_mouse_release();
1918+
1919+ EXPECT_THAT(handle, Ne(nullptr));
1920+ EXPECT_THAT(handle, BlobContentEq(handle_from_request));
1921+}
1922+
1923+TEST_F(DragAndDrop, after_drag_finishes_pointer_events_no_longer_contain_handle)
1924+{
1925+ auto const cookie = user_initiates_drag();
1926+ ASSERT_THAT(cookie, Ne(nullptr));
1927+ client_requests_drag(cookie);
1928+ handle_from_mouse_release();
1929+
1930+ invoke_tools([](miral::WindowManagerTools& tools) { tools.end_drag_and_drop(); });
1931+
1932+ // Allow old pointer events to drain
1933+ std::this_thread::sleep_for(2ms);
1934+
1935+ EXPECT_THAT(count_of_handles_when_moving_mouse(), Eq(0));
1936+}
1937
1938=== added file 'tests/miral/modify_window_state.cpp'
1939--- tests/miral/modify_window_state.cpp 1970-01-01 00:00:00 +0000
1940+++ tests/miral/modify_window_state.cpp 2017-08-24 15:19:58 +0000
1941@@ -0,0 +1,105 @@
1942+/*
1943+ * Copyright © 2016 Canonical Ltd.
1944+ *
1945+ * This program is free software: you can redistribute it and/or modify it
1946+ * under the terms of the GNU General Public License version 2 or 3 as
1947+ * published by the Free Software Foundation.
1948+ *
1949+ * This program is distributed in the hope that it will be useful,
1950+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1951+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1952+ * GNU General Public License for more details.
1953+ *
1954+ * You should have received a copy of the GNU General Public License
1955+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1956+ *
1957+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
1958+ */
1959+
1960+#include "test_window_manager_tools.h"
1961+#include <mir/event_printer.h>
1962+
1963+using namespace miral;
1964+using namespace testing;
1965+namespace mt = mir::test;
1966+using mir::operator<<;
1967+
1968+namespace
1969+{
1970+X const display_left{0};
1971+Y const display_top{0};
1972+Width const display_width{640};
1973+Height const display_height{480};
1974+
1975+Rectangle const display_area{{display_left, display_top},
1976+ {display_width, display_height}};
1977+
1978+struct ModifyWindowState : TestWindowManagerTools, WithParamInterface<MirWindowType>
1979+{
1980+ Size const initial_parent_size{600, 400};
1981+
1982+ Window window;
1983+
1984+ void SetUp() override
1985+ {
1986+ basic_window_manager.add_display_for_testing(display_area);
1987+ basic_window_manager.add_session(session);
1988+ }
1989+
1990+ void create_window_of_type(MirWindowType type)
1991+ {
1992+ mir::scene::SurfaceCreationParameters creation_parameters;
1993+ creation_parameters.type = type;
1994+ creation_parameters.size = initial_parent_size;
1995+
1996+ EXPECT_CALL(*window_manager_policy, advise_new_window(_))
1997+ .WillOnce(
1998+ Invoke(
1999+ [this](WindowInfo const& window_info)
2000+ { window = window_info.window(); }));
2001+
2002+ basic_window_manager.add_surface(session, creation_parameters, &create_surface);
2003+ basic_window_manager.select_active_window(window);
2004+
2005+ // Clear the expectations used to capture parent & child
2006+ Mock::VerifyAndClearExpectations(window_manager_policy);
2007+ }
2008+};
2009+
2010+using ForNormalSurface = ModifyWindowState;
2011+
2012+TEST_P(ForNormalSurface, state)
2013+{
2014+ auto const original_state = mir_window_state_restored;
2015+ auto const new_state = MirWindowState(GetParam());
2016+ auto const state_is_visible = (new_state != mir_window_state_minimized) && (new_state != mir_window_state_hidden);
2017+
2018+ create_window_of_type(mir_window_type_normal);
2019+ auto const& info = window_manager_tools.info_for(window);
2020+
2021+ WindowSpecification mods;
2022+ mods.state() = new_state;
2023+ window_manager_tools.modify_window(window, mods);
2024+ EXPECT_THAT(std::shared_ptr<mir::scene::Surface>(window)->state(), Eq(new_state));
2025+ EXPECT_THAT(info.state(), Eq(new_state));
2026+ EXPECT_THAT(info.is_visible(), Eq(state_is_visible)) << "State is " << new_state;
2027+
2028+ mods.state() = original_state;
2029+ window_manager_tools.modify_window(window, mods);
2030+ EXPECT_THAT(std::shared_ptr<mir::scene::Surface>(window)->state(), Eq(original_state));
2031+ EXPECT_THAT(info.state(), Eq(original_state));
2032+ EXPECT_TRUE(info.is_visible());
2033+}
2034+}
2035+
2036+INSTANTIATE_TEST_CASE_P(ModifyWindowState, ForNormalSurface, ::testing::Values(
2037+// mir_window_state_unknown,
2038+ mir_window_state_restored,
2039+ mir_window_state_minimized,
2040+ mir_window_state_maximized,
2041+ mir_window_state_vertmaximized,
2042+ mir_window_state_fullscreen,
2043+ mir_window_state_horizmaximized,
2044+ mir_window_state_hidden
2045+// mir_window_states
2046+));
2047
2048=== added file 'tests/miral/mru_window_list.cpp'
2049--- tests/miral/mru_window_list.cpp 1970-01-01 00:00:00 +0000
2050+++ tests/miral/mru_window_list.cpp 2017-08-24 15:19:58 +0000
2051@@ -0,0 +1,193 @@
2052+/*
2053+ * Copyright © 2016 Canonical Ltd.
2054+ *
2055+ * This program is free software: you can redistribute it and/or modify it
2056+ * under the terms of the GNU General Public License version 2 or 3 as
2057+ * published by the Free Software Foundation.
2058+ *
2059+ * This program is distributed in the hope that it will be useful,
2060+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2061+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2062+ * GNU General Public License for more details.
2063+ *
2064+ * You should have received a copy of the GNU General Public License
2065+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2066+ *
2067+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
2068+ */
2069+
2070+#include "mru_window_list.h"
2071+
2072+#include <mir/test/doubles/stub_surface.h>
2073+#include <mir/test/doubles/stub_session.h>
2074+
2075+#include <gtest/gtest.h>
2076+#include <gmock/gmock.h>
2077+
2078+using namespace testing;
2079+
2080+namespace
2081+{
2082+struct StubSurface : mir::test::doubles::StubSurface
2083+{
2084+ bool visible() const override { return visible_; }
2085+
2086+ bool visible_ = true;
2087+};
2088+
2089+struct StubSession : mir::test::doubles::StubSession
2090+{
2091+ StubSession(int number_of_surfaces)
2092+ {
2093+ for (auto i = 0; i != number_of_surfaces; ++i)
2094+ surfaces.push_back(std::make_shared<StubSurface>());
2095+ }
2096+
2097+ std::shared_ptr<mir::scene::Surface> surface(mir::frontend::SurfaceId surface) const override
2098+ {
2099+ return surfaces.at(surface.as_value());
2100+ }
2101+
2102+ std::vector<std::shared_ptr<StubSurface>> surfaces;
2103+};
2104+
2105+MATCHER(IsNullWindow, std::string("is not null"))
2106+{
2107+ return !arg;
2108+}
2109+}
2110+
2111+struct MRUWindowList : testing::Test
2112+{
2113+ static auto const window_a_id = 0;
2114+ static auto const window_b_id = 1;
2115+
2116+ miral::MRUWindowList mru_list;
2117+
2118+ std::shared_ptr<StubSession> const stub_session{std::make_shared<StubSession>(3)};
2119+ miral::Application app{stub_session};
2120+ miral::Window window_a{app, stub_session->surface(mir::frontend::SurfaceId{window_a_id})};
2121+ miral::Window window_b{app, stub_session->surface(mir::frontend::SurfaceId{window_b_id})};
2122+ miral::Window window_c{app, stub_session->surface(mir::frontend::SurfaceId{2})};
2123+
2124+ void hide_window(int window_id)
2125+ {
2126+ stub_session->surfaces[window_id]->visible_ = false;
2127+ }
2128+
2129+ void show_window(int window_id)
2130+ {
2131+ stub_session->surfaces[window_id]->visible_ = true;
2132+ }
2133+};
2134+
2135+TEST_F(MRUWindowList, when_created_is_empty)
2136+{
2137+ EXPECT_THAT(mru_list.top(), IsNullWindow());
2138+}
2139+
2140+TEST_F(MRUWindowList, given_empty_list_when_a_window_pushed_that_window_is_top)
2141+{
2142+ mru_list.push(window_a);
2143+ EXPECT_THAT(mru_list.top(), Eq(window_a));
2144+}
2145+
2146+TEST_F(MRUWindowList, given_non_empty_list_when_a_window_pushed_that_window_is_top)
2147+{
2148+ mru_list.push(window_a);
2149+ mru_list.push(window_b);
2150+ mru_list.push(window_c);
2151+ EXPECT_THAT(mru_list.top(), Eq(window_c));
2152+}
2153+
2154+TEST_F(MRUWindowList, given_non_empty_list_when_top_window_is_erased_that_window_is_no_longer_on_top)
2155+{
2156+ mru_list.push(window_a);
2157+ mru_list.push(window_b);
2158+ mru_list.push(window_c);
2159+ mru_list.erase(window_c);
2160+ EXPECT_THAT(mru_list.top(), Ne(window_c));
2161+}
2162+
2163+TEST_F(MRUWindowList, a_window_pushed_twice_is_not_enumerated_twice)
2164+{
2165+ mru_list.push(window_a);
2166+ mru_list.push(window_b);
2167+ mru_list.push(window_a);
2168+
2169+ int count{0};
2170+
2171+ mru_list.enumerate([&](miral::Window& window)
2172+ { if (window == window_a) ++count; return true; });
2173+
2174+ EXPECT_THAT(count, Eq(1));
2175+}
2176+
2177+TEST_F(MRUWindowList, after_multiple_pushes_windows_are_enumerated_in_mru_order)
2178+{
2179+ mru_list.push(window_a);
2180+ mru_list.push(window_b);
2181+ mru_list.push(window_c);
2182+ mru_list.push(window_a);
2183+ mru_list.push(window_b);
2184+ mru_list.push(window_a);
2185+
2186+ mru_list.push(window_c);
2187+ mru_list.push(window_b);
2188+ mru_list.push(window_a);
2189+
2190+ std::vector<miral::Window> as_enumerated;
2191+
2192+ mru_list.enumerate([&](miral::Window& window)
2193+ { as_enumerated.push_back(window); return true; });
2194+
2195+ EXPECT_THAT(as_enumerated, ElementsAre(window_a, window_b, window_c));
2196+}
2197+
2198+TEST_F(MRUWindowList, when_enumerator_returns_false_enumeration_is_short_circuited)
2199+{
2200+ mru_list.push(window_a);
2201+ mru_list.push(window_b);
2202+ mru_list.push(window_c);
2203+
2204+ int count{0};
2205+
2206+ mru_list.enumerate([&](miral::Window&) { ++count; return false; });
2207+
2208+ EXPECT_THAT(count, Eq(1));
2209+}
2210+
2211+TEST_F(MRUWindowList, a_hidden_window_is_not_enumerated)
2212+{
2213+ mru_list.push(window_a);
2214+ mru_list.push(window_b);
2215+ mru_list.push(window_c);
2216+
2217+ int count{0};
2218+
2219+ hide_window(window_a_id);
2220+ mru_list.enumerate([&](miral::Window& window)
2221+ { if (window == window_a) ++count; return true; });
2222+
2223+ EXPECT_THAT(count, Eq(0));
2224+}
2225+
2226+TEST_F(MRUWindowList, hiding_then_showing_windows_retains_order)
2227+{
2228+ mru_list.push(window_a);
2229+ mru_list.push(window_b);
2230+ mru_list.push(window_c);
2231+
2232+ hide_window(window_a_id);
2233+ hide_window(window_b_id);
2234+ show_window(window_a_id);
2235+ show_window(window_b_id);
2236+
2237+ std::vector<miral::Window> as_enumerated;
2238+
2239+ mru_list.enumerate([&](miral::Window& window)
2240+ { as_enumerated.push_back(window); return true; });
2241+
2242+ EXPECT_THAT(as_enumerated, ElementsAre(window_c, window_b, window_a));
2243+}
2244+
2245
2246=== added file 'tests/miral/raise_tree.cpp'
2247--- tests/miral/raise_tree.cpp 1970-01-01 00:00:00 +0000
2248+++ tests/miral/raise_tree.cpp 2017-08-24 15:19:58 +0000
2249@@ -0,0 +1,85 @@
2250+/*
2251+ * Copyright © 2017 Canonical Ltd.
2252+ *
2253+ * This program is free software: you can redistribute it and/or modify it
2254+ * under the terms of the GNU General Public License version 2 or 3 as
2255+ * published by the Free Software Foundation.
2256+ *
2257+ * This program is distributed in the hope that it will be useful,
2258+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2259+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2260+ * GNU General Public License for more details.
2261+ *
2262+ * You should have received a copy of the GNU General Public License
2263+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2264+ *
2265+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
2266+ */
2267+
2268+#include "test_window_manager_tools.h"
2269+
2270+using namespace miral;
2271+using namespace testing;
2272+namespace mt = mir::test;
2273+
2274+namespace
2275+{
2276+X const display_left{0};
2277+Y const display_top{0};
2278+Width const display_width{640};
2279+Height const display_height{480};
2280+
2281+Rectangle const display_area{{display_left, display_top}, {display_width, display_height}};
2282+
2283+struct RaiseTree : TestWindowManagerTools
2284+{
2285+ Size const initial_parent_size{600, 400};
2286+ Size const initial_child_size{300, 300};
2287+
2288+ Window parent;
2289+ Window child;
2290+ Window another_window;
2291+
2292+ void SetUp() override
2293+ {
2294+ basic_window_manager.add_display_for_testing(display_area);
2295+
2296+ mir::scene::SurfaceCreationParameters creation_parameters;
2297+ basic_window_manager.add_session(session);
2298+
2299+ EXPECT_CALL(*window_manager_policy, advise_new_window(_))
2300+ .WillOnce(Invoke([this](WindowInfo const& window_info){ parent = window_info.window(); }))
2301+ .WillOnce(Invoke([this](WindowInfo const& window_info){ child = window_info.window(); }))
2302+ .WillOnce(Invoke([this](WindowInfo const& window_info){ another_window = window_info.window(); }));
2303+
2304+ creation_parameters.size = initial_parent_size;
2305+ basic_window_manager.add_surface(session, creation_parameters, &create_surface);
2306+
2307+ creation_parameters.type = mir_window_type_menu;
2308+ creation_parameters.parent = parent;
2309+ creation_parameters.size = initial_child_size;
2310+ basic_window_manager.add_surface(session, creation_parameters, &create_surface);
2311+
2312+ creation_parameters.type = mir_window_type_normal;
2313+ creation_parameters.parent.reset();
2314+ creation_parameters.size = display_area.size;
2315+ basic_window_manager.add_surface(session, creation_parameters, &create_surface);
2316+
2317+ // Clear the expectations used to capture parent & child
2318+ Mock::VerifyAndClearExpectations(window_manager_policy);
2319+ }
2320+};
2321+}
2322+
2323+TEST_F(RaiseTree, when_parent_is_raised_child_is_raised)
2324+{
2325+ EXPECT_CALL(*window_manager_policy, advise_raise(ElementsAre(parent, child)));
2326+ basic_window_manager.raise_tree(parent);
2327+}
2328+
2329+TEST_F(RaiseTree, when_child_is_raised_parent_is_raised)
2330+{
2331+ EXPECT_CALL(*window_manager_policy, advise_raise(ElementsAre(parent, child)));
2332+ EXPECT_CALL(*window_manager_policy, advise_raise(ElementsAre(child)));
2333+ basic_window_manager.raise_tree(child);
2334+}
2335
2336=== added file 'tests/miral/runner.cpp'
2337--- tests/miral/runner.cpp 1970-01-01 00:00:00 +0000
2338+++ tests/miral/runner.cpp 2017-08-24 15:19:58 +0000
2339@@ -0,0 +1,49 @@
2340+/*
2341+ * Copyright © 2016 Canonical Ltd.
2342+ *
2343+ * This program is free software: you can redistribute it and/or modify it
2344+ * under the terms of the GNU General Public License version 2 or 3 as
2345+ * published by the Free Software Foundation.
2346+ *
2347+ * This program is distributed in the hope that it will be useful,
2348+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2349+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2350+ * GNU General Public License for more details.
2351+ *
2352+ * You should have received a copy of the GNU General Public License
2353+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2354+ *
2355+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
2356+ */
2357+
2358+#include "test_server.h"
2359+
2360+#include <gtest/gtest.h>
2361+#include <gmock/gmock.h>
2362+
2363+namespace
2364+{
2365+struct Runner : miral::TestServer
2366+{
2367+ void SetUp() override
2368+ {
2369+ }
2370+
2371+ MOCK_METHOD0(callback, void());
2372+};
2373+}
2374+
2375+TEST_F(Runner, stop_callback_is_called)
2376+{
2377+ runner.add_stop_callback([this] { callback(); });
2378+ miral::TestServer::SetUp();
2379+ EXPECT_CALL(*this, callback());
2380+}
2381+
2382+TEST_F(Runner, start_callback_is_called)
2383+{
2384+ runner.add_start_callback([this] { callback(); });
2385+ EXPECT_CALL(*this, callback());
2386+ miral::TestServer::SetUp();
2387+ testing::Mock::VerifyAndClearExpectations(this);
2388+}
2389\ No newline at end of file
2390
2391=== added file 'tests/miral/select_active_window.cpp'
2392--- tests/miral/select_active_window.cpp 1970-01-01 00:00:00 +0000
2393+++ tests/miral/select_active_window.cpp 2017-08-24 15:19:58 +0000
2394@@ -0,0 +1,121 @@
2395+/*
2396+ * Copyright © 2016 Canonical Ltd.
2397+ *
2398+ * This program is free software: you can redistribute it and/or modify it
2399+ * under the terms of the GNU General Public License version 2 or 3 as
2400+ * published by the Free Software Foundation.
2401+ *
2402+ * This program is distributed in the hope that it will be useful,
2403+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2404+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2405+ * GNU General Public License for more details.
2406+ *
2407+ * You should have received a copy of the GNU General Public License
2408+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2409+ *
2410+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
2411+ */
2412+
2413+#include "test_window_manager_tools.h"
2414+
2415+using namespace miral;
2416+using namespace testing;
2417+namespace mt = mir::test;
2418+
2419+namespace
2420+{
2421+X const display_left{0};
2422+Y const display_top{0};
2423+Width const display_width{1280};
2424+Height const display_height{720};
2425+
2426+Rectangle const display_area{{display_left, display_top},
2427+ {display_width, display_height}};
2428+
2429+struct SelectActiveWindow : TestWindowManagerTools
2430+{
2431+
2432+ void SetUp() override
2433+ {
2434+ basic_window_manager.add_display_for_testing(display_area);
2435+ basic_window_manager.add_session(session);
2436+ }
2437+
2438+ auto create_window(mir::scene::SurfaceCreationParameters creation_parameters) -> Window
2439+ {
2440+ Window result;
2441+
2442+ EXPECT_CALL(*window_manager_policy, advise_new_window(_))
2443+ .WillOnce(
2444+ Invoke(
2445+ [&result](WindowInfo const& window_info)
2446+ { result = window_info.window(); }));
2447+
2448+ basic_window_manager.add_surface(session, creation_parameters, &create_surface);
2449+ basic_window_manager.select_active_window(result);
2450+
2451+ // Clear the expectations used to capture parent & child
2452+ Mock::VerifyAndClearExpectations(window_manager_policy);
2453+
2454+ return result;
2455+ }
2456+};
2457+}
2458+
2459+namespace std
2460+{
2461+auto operator<<(std::ostream& out, miral::Window const& window) -> std::ostream&
2462+{
2463+ if (std::shared_ptr<mir::scene::Surface> surface = window)
2464+ return out << surface->name();
2465+ else
2466+ return out << "(nul)";
2467+}
2468+}
2469+
2470+// lp:1626659
2471+// "If the surface has a child dialog, the deepest descendant
2472+// dialog should receive input focus."
2473+TEST_F(SelectActiveWindow, given_a_child_dialog_when_selecting_the_parent_the_dialog_receives_focus)
2474+{
2475+ mir::scene::SurfaceCreationParameters creation_parameters;
2476+ creation_parameters.name = "parent";
2477+ creation_parameters.type = mir_window_type_normal;
2478+ creation_parameters.size = Size{600, 400};
2479+
2480+ auto parent = create_window(creation_parameters);
2481+
2482+ creation_parameters.name = "dialog";
2483+ creation_parameters.type = mir_window_type_dialog;
2484+ creation_parameters.parent = parent;
2485+
2486+ auto dialog = create_window(creation_parameters);
2487+
2488+ auto actual = basic_window_manager.select_active_window(parent);
2489+ EXPECT_THAT(actual, Eq(dialog))
2490+ << "actual=" << actual << ", expected=" << dialog;
2491+}
2492+
2493+TEST_F(SelectActiveWindow, given_a_hidden_child_dialog_when_selecting_the_parent_the_parent_receives_focus)
2494+{
2495+ mir::scene::SurfaceCreationParameters creation_parameters;
2496+ creation_parameters.name = "parent";
2497+ creation_parameters.type = mir_window_type_normal;
2498+ creation_parameters.size = Size{600, 400};
2499+
2500+ auto parent = create_window(creation_parameters);
2501+
2502+ creation_parameters.name = "dialog";
2503+ creation_parameters.type = mir_window_type_dialog;
2504+ creation_parameters.parent = parent;
2505+
2506+ auto dialog = create_window(creation_parameters);
2507+
2508+ WindowSpecification mods;
2509+ mods.state() = mir_window_state_hidden;
2510+ basic_window_manager.modify_window(basic_window_manager.info_for(dialog), mods);
2511+
2512+ auto actual = basic_window_manager.select_active_window(parent);
2513+ EXPECT_THAT(actual, Eq(parent))
2514+ << "actual=" << actual << ", expected=" << parent;
2515+}
2516
2517=== added file 'tests/miral/test_server.cpp'
2518--- tests/miral/test_server.cpp 1970-01-01 00:00:00 +0000
2519+++ tests/miral/test_server.cpp 2017-08-24 15:19:58 +0000
2520@@ -0,0 +1,198 @@
2521+/*
2522+ * Copyright © 2016 Canonical Ltd.
2523+ *
2524+ * This program is free software: you can redistribute it and/or modify it
2525+ * under the terms of the GNU General Public License version 2 or 3 as
2526+ * published by the Free Software Foundation.
2527+ *
2528+ * This program is distributed in the hope that it will be useful,
2529+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2530+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2531+ * GNU General Public License for more details.
2532+ *
2533+ * You should have received a copy of the GNU General Public License
2534+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2535+ *
2536+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
2537+ */
2538+
2539+#include "test_server.h"
2540+#include "basic_window_manager.h"
2541+
2542+#include <miral/command_line_option.h>
2543+#include <miral/set_window_management_policy.h>
2544+
2545+#include <mir_test_framework/executable_path.h>
2546+#include <mir_test_framework/stub_server_platform_factory.h>
2547+#include <mir_test_framework/headless_display_buffer_compositor_factory.h>
2548+#include <mir/test/doubles/null_logger.h>
2549+
2550+#include <mir/fd.h>
2551+#include <mir/main_loop.h>
2552+#include <mir/server.h>
2553+#include <mir/version.h>
2554+
2555+#include <boost/throw_exception.hpp>
2556+
2557+
2558+using namespace miral;
2559+using namespace testing;
2560+namespace mtf = mir_test_framework;
2561+namespace msh = mir::shell;
2562+
2563+namespace
2564+{
2565+std::chrono::seconds const timeout{20};
2566+char const* dummy_args[2] = { "TestServer", nullptr };
2567+}
2568+
2569+miral::TestServer::TestWindowManagerPolicy::TestWindowManagerPolicy(
2570+ WindowManagerTools const& tools, TestServer& test_fixture) :
2571+ CanonicalWindowManagerPolicy{tools}
2572+{
2573+ test_fixture.tools = tools;
2574+}
2575+
2576+miral::TestServer::TestServer() :
2577+ runner{1, dummy_args}
2578+{
2579+ add_to_environment("MIR_SERVER_PLATFORM_GRAPHICS_LIB", mtf::server_platform("graphics-dummy.so").c_str());
2580+ add_to_environment("MIR_SERVER_PLATFORM_INPUT_LIB", mtf::server_platform("input-stub.so").c_str());
2581+ add_to_environment("MIR_SERVER_NO_FILE", "on");
2582+}
2583+
2584+auto miral::TestServer::build_window_manager_policy(WindowManagerTools const& tools)
2585+-> std::unique_ptr<TestWindowManagerPolicy>
2586+{
2587+ return std::make_unique<TestWindowManagerPolicy>(tools, *this);
2588+}
2589+
2590+void miral::TestServer::SetUp()
2591+{
2592+ mir::test::AutoJoinThread t([this]
2593+ {
2594+ auto init = [this](mir::Server& server)
2595+ {
2596+ server.add_init_callback([&]
2597+ {
2598+ auto const main_loop = server.the_main_loop();
2599+ // By enqueuing the notification code in the main loop, we are
2600+ // ensuring that the server has really and fully started before
2601+ // leaving start_mir_server().
2602+ main_loop->enqueue(this, [&]
2603+ {
2604+ std::lock_guard<std::mutex> lock(mutex);
2605+ server_running = &server;
2606+ started.notify_one();
2607+ });
2608+ });
2609+
2610+ server.override_the_display_buffer_compositor_factory([]
2611+ {
2612+ return std::make_shared<mtf::HeadlessDisplayBufferCompositorFactory>();
2613+ });
2614+
2615+ server.override_the_window_manager_builder([this, &server](msh::FocusController* focus_controller)
2616+ -> std::shared_ptr<msh::WindowManager>
2617+ {
2618+ auto const display_layout = server.the_shell_display_layout();
2619+
2620+ auto const persistent_surface_store = server.the_persistent_surface_store();
2621+
2622+ auto builder = [this](WindowManagerTools const& tools) -> std::unique_ptr<miral::WindowManagementPolicy>
2623+ {
2624+ return build_window_manager_policy(tools);
2625+ };
2626+
2627+ auto wm = std::make_shared<miral::BasicWindowManager>(
2628+ focus_controller,
2629+ display_layout,
2630+ persistent_surface_store,
2631+ *server.the_display_configuration_observer_registrar(),
2632+ builder);
2633+ window_manager = wm;
2634+ return wm;
2635+ });
2636+ };
2637+
2638+ try
2639+ {
2640+ namespace mtd = mir::test::doubles;
2641+ // Ignore the --logging flag passed to mir tests
2642+ CommandLineOption logging{[](bool) {}, mtd::logging_opt, mtd::logging_descr, false};
2643+ runner.run_with({init, logging});
2644+ }
2645+ catch (std::exception const& e)
2646+ {
2647+ FAIL() << e.what();
2648+ }
2649+
2650+ std::lock_guard<std::mutex> lock(mutex);
2651+ server_running = nullptr;
2652+ started.notify_one();
2653+ });
2654+
2655+ std::unique_lock<std::mutex> lock(mutex);
2656+ started.wait_for(lock, timeout, [&] { return server_running; });
2657+
2658+ if (!server_running)
2659+ BOOST_THROW_EXCEPTION(std::runtime_error{"Failed to start server thread"});
2660+
2661+ server_thread = std::move(t);
2662+}
2663+
2664+void miral::TestServer::TearDown()
2665+{
2666+ std::unique_lock<std::mutex> lock(mutex);
2667+
2668+ runner.stop();
2669+
2670+ started.wait_for(lock, timeout, [&] { return !server_running; });
2671+
2672+ if (server_running)
2673+ BOOST_THROW_EXCEPTION(std::logic_error{"Failed to stop server"});
2674+
2675+ server_thread.stop();
2676+}
2677+
2678+auto miral::TestServer::connect_client(std::string name) -> mir::client::Connection
2679+{
2680+ std::lock_guard<std::mutex> lock(mutex);
2681+
2682+ if (!server_running)
2683+ BOOST_THROW_EXCEPTION(std::runtime_error{"Server not running"});
2684+
2685+ char connect_string[64] = {0};
2686+ sprintf(connect_string, "fd://%d", dup(server_running->open_client_socket()));
2687+
2688+ return mir::client::Connection{mir_connect_sync(connect_string, name.c_str())};
2689+}
2690+
2691+void miral::TestServer::invoke_tools(std::function<void(WindowManagerTools& tools)> const& f)
2692+{
2693+ tools.invoke_under_lock([&]{f(tools); });
2694+}
2695+
2696+void miral::TestServer::invoke_window_manager(std::function<void(mir::shell::WindowManager& wm)> const& f)
2697+{
2698+ if (auto const wm = window_manager.lock())
2699+ f(*wm);
2700+ else
2701+ BOOST_THROW_EXCEPTION(std::runtime_error{"Server not running"});
2702+
2703+}
2704+
2705+void miral::TestRuntimeEnvironment::add_to_environment(char const* key, char const* value)
2706+{
2707+ env.emplace_back(key, value);
2708+}
2709+
2710+using miral::TestServer;
2711+
2712+// Minimal test to ensure the server runs and exits
2713+TEST_F(TestServer, connect_client_works)
2714+{
2715+ auto const connection = connect_client(__PRETTY_FUNCTION__);
2716+
2717+ EXPECT_TRUE(mir_connection_is_valid(connection));
2718+}
2719
2720=== added file 'tests/miral/test_server.h'
2721--- tests/miral/test_server.h 1970-01-01 00:00:00 +0000
2722+++ tests/miral/test_server.h 2017-08-24 15:19:58 +0000
2723@@ -0,0 +1,90 @@
2724+/*
2725+ * Copyright © 2016 Canonical Ltd.
2726+ *
2727+ * This program is free software: you can redistribute it and/or modify it
2728+ * under the terms of the GNU General Public License version 2 or 3 as
2729+ * published by the Free Software Foundation.
2730+ *
2731+ * This program is distributed in the hope that it will be useful,
2732+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2733+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2734+ * GNU General Public License for more details.
2735+ *
2736+ * You should have received a copy of the GNU General Public License
2737+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2738+ *
2739+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
2740+ */
2741+
2742+#ifndef MIRAL_TEST_SERVER_H
2743+#define MIRAL_TEST_SERVER_H
2744+
2745+#include <mir/client/connection.h>
2746+
2747+#include <miral/canonical_window_manager.h>
2748+#include <miral/runner.h>
2749+#include <miral/window_manager_tools.h>
2750+
2751+#include <mir/test/auto_unblock_thread.h>
2752+#include <mir_test_framework/temporary_environment_value.h>
2753+
2754+#include <gtest/gtest.h>
2755+
2756+#include <condition_variable>
2757+#include <list>
2758+#include <mutex>
2759+
2760+namespace mir { namespace shell { class WindowManager; }}
2761+
2762+namespace miral
2763+{
2764+class WindowManagementPolicy;
2765+class TestRuntimeEnvironment
2766+{
2767+public:
2768+ void add_to_environment(char const* key, char const* value);
2769+
2770+private:
2771+ std::list<mir_test_framework::TemporaryEnvironmentValue> env;
2772+};
2773+
2774+struct TestServer : testing::Test, private TestRuntimeEnvironment
2775+{
2776+ TestServer();
2777+
2778+ void SetUp() override;
2779+ void TearDown() override;
2780+
2781+ auto connect_client(std::string name) -> mir::client::Connection;
2782+
2783+ using TestRuntimeEnvironment::add_to_environment;
2784+
2785+ MirRunner runner;
2786+
2787+ void invoke_tools(std::function<void(WindowManagerTools& tools)> const& f);
2788+ void invoke_window_manager(std::function<void(mir::shell::WindowManager& wm)> const& f);
2789+
2790+ struct TestWindowManagerPolicy;
2791+ virtual auto build_window_manager_policy(WindowManagerTools const& tools) -> std::unique_ptr<TestWindowManagerPolicy>;
2792+
2793+private:
2794+ WindowManagerTools tools{nullptr};
2795+ std::weak_ptr<mir::shell::WindowManager> window_manager;
2796+ mir::test::AutoJoinThread server_thread;
2797+ std::mutex mutex;
2798+ std::condition_variable started;
2799+ mir::Server* server_running{nullptr};
2800+};
2801+
2802+struct TestServer::TestWindowManagerPolicy : CanonicalWindowManagerPolicy
2803+{
2804+ TestWindowManagerPolicy(WindowManagerTools const& tools, TestServer& test_fixture);
2805+
2806+ bool handle_keyboard_event(MirKeyboardEvent const*) override { return false; }
2807+ bool handle_pointer_event(MirPointerEvent const*) override { return false; }
2808+ bool handle_touch_event(MirTouchEvent const*) override { return false; }
2809+};
2810+
2811+}
2812+
2813+#endif //MIRAL_TEST_SERVER_H
2814
2815=== added file 'tests/miral/test_window_manager_tools.h'
2816--- tests/miral/test_window_manager_tools.h 1970-01-01 00:00:00 +0000
2817+++ tests/miral/test_window_manager_tools.h 2017-08-24 15:19:58 +0000
2818@@ -0,0 +1,197 @@
2819+/*
2820+ * Copyright © 2016 Canonical Ltd.
2821+ *
2822+ * This program is free software: you can redistribute it and/or modify it
2823+ * under the terms of the GNU General Public License version 2 or 3 as
2824+ * published by the Free Software Foundation.
2825+ *
2826+ * This program is distributed in the hope that it will be useful,
2827+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2828+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2829+ * GNU General Public License for more details.
2830+ *
2831+ * You should have received a copy of the GNU General Public License
2832+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2833+ *
2834+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
2835+ */
2836+
2837+#ifndef MIRAL_TEST_WINDOW_MANAGER_TOOLS_H
2838+#define MIRAL_TEST_WINDOW_MANAGER_TOOLS_H
2839+
2840+#include "basic_window_manager.h"
2841+
2842+#include <miral/canonical_window_manager.h>
2843+
2844+#include <mir/scene/surface_creation_parameters.h>
2845+#include <mir/shell/display_layout.h>
2846+#include <mir/shell/focus_controller.h>
2847+#include <mir/shell/persistent_surface_store.h>
2848+#include <mir/version.h>
2849+
2850+#include <mir/test/doubles/stub_session.h>
2851+#include <mir/test/doubles/stub_surface.h>
2852+#include <mir/test/fake_shared.h>
2853+
2854+#include <gtest/gtest.h>
2855+#include <gmock/gmock.h>
2856+
2857+#include <atomic>
2858+
2859+struct StubFocusController : mir::shell::FocusController
2860+{
2861+ void focus_next_session() override {}
2862+
2863+ auto focused_session() const -> std::shared_ptr<mir::scene::Session> override { return {}; }
2864+
2865+ void set_focus_to(
2866+ std::shared_ptr<mir::scene::Session> const& /*focus_session*/,
2867+ std::shared_ptr<mir::scene::Surface> const& /*focus_surface*/) override {}
2868+
2869+ auto focused_surface() const -> std::shared_ptr<mir::scene::Surface> override { return {}; }
2870+
2871+ void raise(mir::shell::SurfaceSet const& /*windows*/) override {}
2872+
2873+ virtual auto surface_at(mir::geometry::Point /*cursor*/) const -> std::shared_ptr<mir::scene::Surface> override
2874+ { return {}; }
2875+
2876+#if MIR_SERVER_VERSION >= MIR_VERSION_NUMBER(0, 27, 0)
2877+ void set_drag_and_drop_handle(std::vector<uint8_t> const& /*handle*/) override {}
2878+
2879+ void clear_drag_and_drop_handle() override {}
2880+#endif
2881+};
2882+
2883+struct StubDisplayLayout : mir::shell::DisplayLayout
2884+{
2885+ void clip_to_output(mir::geometry::Rectangle& /*rect*/) override {}
2886+
2887+ void size_to_output(mir::geometry::Rectangle& /*rect*/) override {}
2888+
2889+ bool place_in_output(mir::graphics::DisplayConfigurationOutputId /*id*/, mir::geometry::Rectangle& /*rect*/) override
2890+ { return false; }
2891+};
2892+
2893+struct StubPersistentSurfaceStore : mir::shell::PersistentSurfaceStore
2894+{
2895+ Id id_for_surface(std::shared_ptr<mir::scene::Surface> const& /*surface*/) override { return {}; }
2896+
2897+ auto surface_for_id(Id const& /*id*/) const -> std::shared_ptr<mir::scene::Surface> override { return {}; }
2898+};
2899+
2900+struct StubSurface : mir::test::doubles::StubSurface
2901+{
2902+ StubSurface(std::string name, MirWindowType type, mir::geometry::Point top_left, mir::geometry::Size size) :
2903+ name_{name}, type_{type}, top_left_{top_left}, size_{size} {}
2904+
2905+ std::string name() const override { return name_; };
2906+ MirWindowType type() const override { return type_; }
2907+
2908+ mir::geometry::Point top_left() const override { return top_left_; }
2909+ void move_to(mir::geometry::Point const& top_left) override { top_left_ = top_left; }
2910+
2911+ mir::geometry::Size size() const override { return size_; }
2912+ void resize(mir::geometry::Size const& size) override { size_ = size; }
2913+
2914+ auto state() const -> MirWindowState override { return state_; }
2915+ auto configure(MirWindowAttrib attrib, int value) -> int override {
2916+ switch (attrib)
2917+ {
2918+ case mir_window_attrib_state:
2919+ state_ = MirWindowState(value);
2920+ return state_;
2921+ default:
2922+ return value;
2923+ }
2924+ }
2925+
2926+ bool visible() const override { return state() != mir_window_state_hidden; }
2927+
2928+ std::string name_;
2929+ MirWindowType type_;
2930+ mir::geometry::Point top_left_;
2931+ mir::geometry::Size size_;
2932+ MirWindowState state_ = mir_window_state_restored;
2933+};
2934+
2935+struct StubStubSession : mir::test::doubles::StubSession
2936+{
2937+ mir::frontend::SurfaceId create_surface(
2938+ mir::scene::SurfaceCreationParameters const& params,
2939+ std::shared_ptr<mir::frontend::EventSink> const& /*sink*/) override
2940+ {
2941+ auto id = mir::frontend::SurfaceId{next_surface_id.fetch_add(1)};
2942+ auto surface = std::make_shared<StubSurface>(params.name, params.type.value(), params.top_left, params.size);
2943+ surfaces[id] = surface;
2944+ return id;
2945+ }
2946+
2947+ std::shared_ptr<mir::scene::Surface> surface(mir::frontend::SurfaceId surface) const override
2948+ {
2949+ return surfaces.at(surface);
2950+ }
2951+
2952+private:
2953+ std::atomic<int> next_surface_id;
2954+ std::map<mir::frontend::SurfaceId, std::shared_ptr<mir::scene::Surface>> surfaces;
2955+};
2956+
2957+struct MockWindowManagerPolicy : miral::CanonicalWindowManagerPolicy
2958+{
2959+ using miral::CanonicalWindowManagerPolicy::CanonicalWindowManagerPolicy;
2960+
2961+ bool handle_touch_event(MirTouchEvent const* /*event*/) { return false; }
2962+ bool handle_pointer_event(MirPointerEvent const* /*event*/) { return false; }
2963+ bool handle_keyboard_event(MirKeyboardEvent const* /*event*/) { return false; }
2964+
2965+ MOCK_METHOD1(advise_new_window, void (miral::WindowInfo const& window_info));
2966+ MOCK_METHOD2(advise_move_to, void(miral::WindowInfo const& window_info, mir::geometry::Point top_left));
2967+ MOCK_METHOD2(advise_resize, void(miral::WindowInfo const& window_info, mir::geometry::Size const& new_size));
2968+ MOCK_METHOD1(advise_raise, void(std::vector<miral::Window> const&));
2969+};
2970+
2971+struct StubDisplayConfigurationObserver : mir::ObserverRegistrar<mir::graphics::DisplayConfigurationObserver>
2972+{
2973+ void register_interest(std::weak_ptr<mir::graphics::DisplayConfigurationObserver> const&) override {}
2974+
2975+ void register_interest(std::weak_ptr<mir::graphics::DisplayConfigurationObserver> const&, mir::Executor&) override {}
2976+
2977+ void unregister_interest(mir::graphics::DisplayConfigurationObserver const&) override {}
2978+};
2979+
2980+struct TestWindowManagerTools : testing::Test
2981+{
2982+ StubFocusController focus_controller;
2983+ StubDisplayLayout display_layout;
2984+ StubPersistentSurfaceStore persistent_surface_store;
2985+ StubDisplayConfigurationObserver display_configuration_observer;
2986+ std::shared_ptr<StubStubSession> session{std::make_shared<StubStubSession>()};
2987+
2988+ MockWindowManagerPolicy* window_manager_policy{nullptr};
2989+ miral::WindowManagerTools window_manager_tools{nullptr};
2990+
2991+ miral::BasicWindowManager basic_window_manager{
2992+ &focus_controller,
2993+ mir::test::fake_shared(display_layout),
2994+ mir::test::fake_shared(persistent_surface_store),
2995+ display_configuration_observer,
2996+ [this](miral::WindowManagerTools const& tools) -> std::unique_ptr<miral::WindowManagementPolicy>
2997+ {
2998+ auto policy = std::make_unique<testing::NiceMock<MockWindowManagerPolicy>>(tools);
2999+ window_manager_policy = policy.get();
3000+ window_manager_tools = tools;
3001+ return std::move(policy);
3002+ }
3003+ };
3004+
3005+ static auto create_surface(
3006+ std::shared_ptr<mir::scene::Session> const& session,
3007+ mir::scene::SurfaceCreationParameters const& params) -> mir::frontend::SurfaceId
3008+ {
3009+ // This type is Mir-internal, I hope we don't need to create it here
3010+ std::shared_ptr<mir::frontend::EventSink> const sink;
3011+ return session->create_surface(params, sink);
3012+ }
3013+};
3014+
3015+#endif //MIRAL_TEST_WINDOW_MANAGER_TOOLS_H
3016
3017=== added file 'tests/miral/window_id.cpp'
3018--- tests/miral/window_id.cpp 1970-01-01 00:00:00 +0000
3019+++ tests/miral/window_id.cpp 2017-08-24 15:19:58 +0000
3020@@ -0,0 +1,114 @@
3021+/*
3022+ * Copyright © 2016 Canonical Ltd.
3023+ *
3024+ * This program is free software: you can redistribute it and/or modify it
3025+ * under the terms of the GNU General Public License version 2 or 3 as
3026+ * published by the Free Software Foundation.
3027+ *
3028+ * This program is distributed in the hope that it will be useful,
3029+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3030+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3031+ * GNU General Public License for more details.
3032+ *
3033+ * You should have received a copy of the GNU General Public License
3034+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3035+ *
3036+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
3037+ */
3038+
3039+#include <mir/client/window_id.h>
3040+#include <mir/client/window.h>
3041+#include <mir/client/window_spec.h>
3042+
3043+#include <miral/application_info.h>
3044+
3045+#include <mir/version.h>
3046+
3047+#include <gtest/gtest.h>
3048+#include <gmock/gmock.h>
3049+
3050+#include "test_server.h"
3051+
3052+using namespace testing;
3053+
3054+
3055+struct WindowId : public miral::TestServer
3056+{
3057+ auto get_first_window(miral::WindowManagerTools& tools) -> miral::Window
3058+ {
3059+ auto app = tools.find_application([&](miral::ApplicationInfo const& /*info*/){return true;});
3060+ auto app_info = tools.info_for(app);
3061+ return app_info.windows().at(0);
3062+ }
3063+};
3064+
3065+TEST_F(WindowId, server_can_identify_window_specified_by_client)
3066+{
3067+ char const* const test_name = __PRETTY_FUNCTION__;
3068+ using namespace mir::client;
3069+
3070+ auto const connection = connect_client(test_name);
3071+ auto const spec = WindowSpec::for_normal_window(connection, 50, 50)
3072+ .set_name(test_name);
3073+
3074+ Window const surface{spec.create_window()};
3075+
3076+ mir::client::WindowId client_surface_id{surface};
3077+
3078+ invoke_tools([&](miral::WindowManagerTools& tools)
3079+ {
3080+ auto const& window_info = tools.info_for_window_id(client_surface_id.c_str());
3081+
3082+ ASSERT_TRUE(window_info.window());
3083+ ASSERT_THAT(window_info.name(), Eq(test_name));
3084+ });
3085+}
3086+
3087+TEST_F(WindowId, server_returns_correct_id_for_window)
3088+{
3089+ char const* const test_name = __PRETTY_FUNCTION__;
3090+ using namespace mir::client;
3091+
3092+ auto const connection = connect_client(test_name);
3093+ auto const spec = WindowSpec::for_normal_window(connection, 50, 50)
3094+ .set_name(test_name);
3095+
3096+ Window const surface{spec.create_window()};
3097+
3098+ mir::client::WindowId client_surface_id{surface};
3099+
3100+ invoke_tools([&](miral::WindowManagerTools& tools)
3101+ {
3102+ auto window = get_first_window(tools);
3103+ auto id = tools.id_for_window(window);
3104+
3105+ ASSERT_THAT(client_surface_id.c_str(), Eq(id));
3106+ });
3107+}
3108+
3109+TEST_F(WindowId, server_fails_gracefully_to_identify_window_from_garbage_id)
3110+{
3111+ char const* const test_name = __PRETTY_FUNCTION__;
3112+ using namespace mir::client;
3113+
3114+ auto const connection = connect_client(test_name);
3115+ auto const spec = WindowSpec::for_normal_window(connection, 50, 50).set_name(test_name);
3116+
3117+ Window const surface{spec.create_window()};
3118+
3119+ mir::client::WindowId client_surface_id{surface};
3120+
3121+ invoke_tools([](miral::WindowManagerTools& tools)
3122+ {
3123+ EXPECT_THROW(tools.info_for_window_id("garbage"), std::exception);
3124+ });
3125+}
3126+
3127+TEST_F(WindowId, server_fails_gracefully_when_id_for_null_window_requested)
3128+{
3129+ invoke_tools([](miral::WindowManagerTools& tools)
3130+ {
3131+ miral::Window window;
3132+ EXPECT_THROW(tools.id_for_window(window), std::runtime_error);
3133+ });
3134+}
3135
3136=== added file 'tests/miral/window_placement.cpp'
3137--- tests/miral/window_placement.cpp 1970-01-01 00:00:00 +0000
3138+++ tests/miral/window_placement.cpp 2017-08-24 15:19:58 +0000
3139@@ -0,0 +1,554 @@
3140+/*
3141+ * Copyright © 2016 Canonical Ltd.
3142+ *
3143+ * This program is free software: you can redistribute it and/or modify it
3144+ * under the terms of the GNU General Public License version 2 or 3 as
3145+ * published by the Free Software Foundation.
3146+ *
3147+ * This program is distributed in the hope that it will be useful,
3148+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3149+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3150+ * GNU General Public License for more details.
3151+ *
3152+ * You should have received a copy of the GNU General Public License
3153+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3154+ *
3155+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
3156+ */
3157+
3158+#include "test_window_manager_tools.h"
3159+
3160+using namespace miral;
3161+using namespace testing;
3162+namespace mt = mir::test;
3163+
3164+namespace
3165+{
3166+X const display_left{0};
3167+Y const display_top{0};
3168+Width const display_width{640};
3169+Height const display_height{480};
3170+
3171+Rectangle const display_area{{display_left, display_top}, {display_width, display_height}};
3172+
3173+auto const null_window = Window{};
3174+
3175+mir::shell::SurfaceSpecification edge_attachment(Rectangle const& aux_rect, MirEdgeAttachment attachment)
3176+{
3177+ mir::shell::SurfaceSpecification result;
3178+ result.aux_rect = aux_rect;
3179+ result.edge_attachment = attachment;
3180+ return result;
3181+}
3182+
3183+struct WindowPlacement : TestWindowManagerTools
3184+{
3185+ Size const initial_parent_size{600, 400};
3186+ Size const initial_child_size{300, 300};
3187+ Rectangle const rectangle_away_from_rhs{{20, 20}, {20, 20}};
3188+ Rectangle const rectangle_near_rhs{{590, 20}, {10, 20}};
3189+ Rectangle const rectangle_away_from_bottom{{20, 20}, {20, 20}};
3190+ Rectangle const rectangle_near_bottom{{20, 380}, {20, 20}};
3191+ Rectangle const rectangle_near_both_sides{{0, 20}, {600, 20}};
3192+ Rectangle const rectangle_near_both_sides_and_bottom{{0, 380}, {600, 20}};
3193+ Rectangle const rectangle_near_all_sides{{0, 20}, {600, 380}};
3194+ Rectangle const rectangle_near_both_bottom_right{{400, 380}, {200, 20}};
3195+
3196+ Window parent;
3197+ Window child;
3198+
3199+ WindowSpecification modification;
3200+
3201+ void SetUp() override
3202+ {
3203+ basic_window_manager.add_display_for_testing(display_area);
3204+
3205+ mir::scene::SurfaceCreationParameters creation_parameters;
3206+ basic_window_manager.add_session(session);
3207+
3208+ EXPECT_CALL(*window_manager_policy, advise_new_window(_))
3209+ .WillOnce(Invoke([this](WindowInfo const& window_info){ parent = window_info.window(); }))
3210+ .WillOnce(Invoke([this](WindowInfo const& window_info){ child = window_info.window(); }));
3211+
3212+ creation_parameters.size = initial_parent_size;
3213+ basic_window_manager.add_surface(session, creation_parameters, &create_surface);
3214+
3215+ creation_parameters.type = mir_window_type_menu;
3216+ creation_parameters.parent = parent;
3217+ creation_parameters.size = initial_child_size;
3218+ basic_window_manager.add_surface(session, creation_parameters, &create_surface);
3219+
3220+ // Clear the expectations used to capture parent & child
3221+ Mock::VerifyAndClearExpectations(window_manager_policy);
3222+ }
3223+
3224+ void TearDown() override
3225+ {
3226+// std::cerr << "DEBUG parent position:" << Rectangle{parent.top_left(), parent.size()} << '\n';
3227+// std::cerr << "DEBUG child position :" << Rectangle{child.top_left(), child.size()} << '\n';
3228+ }
3229+
3230+ auto aux_rect_position() -> Rectangle
3231+ {
3232+ auto const rectangle = modification.aux_rect().value();
3233+ return {rectangle.top_left + (parent.top_left() - Point{}), rectangle.size};
3234+ }
3235+
3236+ auto on_top_edge() -> Point
3237+ {
3238+ return aux_rect_position().top_left - as_displacement(child.size()).dy;
3239+ }
3240+
3241+ auto on_right_edge() -> Point
3242+ {
3243+ return aux_rect_position().top_right();
3244+ }
3245+
3246+ auto on_left_edge() -> Point
3247+ {
3248+ return aux_rect_position().top_left - as_displacement(child.size()).dx;
3249+ }
3250+
3251+ auto on_bottom_edge() -> Point
3252+ {
3253+ return aux_rect_position().bottom_left();
3254+ }
3255+};
3256+}
3257+
3258+TEST_F(WindowPlacement, fixture_sets_up_parent_and_child)
3259+{
3260+ ASSERT_THAT(parent, Ne(null_window));
3261+ ASSERT_THAT(parent.size(), Eq(initial_parent_size));
3262+ ASSERT_THAT(basic_window_manager.info_for(parent).children(), ElementsAre(child));
3263+ ASSERT_THAT(basic_window_manager.info_for(parent).type(), Eq(mir_window_type_normal));
3264+
3265+ ASSERT_THAT(child, Ne(null_window));
3266+ ASSERT_THAT(child.size(), Eq(initial_child_size));
3267+ ASSERT_THAT(basic_window_manager.info_for(child).parent(), Eq(parent));
3268+ ASSERT_THAT(basic_window_manager.info_for(child).type(), Eq(mir_window_type_menu));
3269+}
3270+
3271+
3272+/* From the Mir client API:
3273+ * Positioning of the surface is specified with respect to the parent surface
3274+ * via an adjacency rectangle. The server will attempt to choose an edge of the
3275+ * adjacency rectangle on which to place the surface taking in to account
3276+ * screen-edge proximity or similar constraints. In addition, the server can use
3277+ * the edge affinity hint to consider only horizontal or only vertical adjacency
3278+ * edges in the given rectangle.
3279+ */
3280+
3281+TEST_F(WindowPlacement, given_aux_rect_away_from_right_side_edge_attachment_vertical_attaches_to_right_edge)
3282+{
3283+ modification = edge_attachment(rectangle_away_from_rhs, mir_edge_attachment_vertical);
3284+
3285+ auto const expected_position = on_right_edge();
3286+
3287+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
3288+ basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
3289+ ASSERT_THAT(child.top_left(), Eq(expected_position));
3290+}
3291+
3292+TEST_F(WindowPlacement, given_aux_rect_near_right_side_edge_attachment_vertical_attaches_to_left_edge)
3293+{
3294+ modification = edge_attachment(rectangle_near_rhs, mir_edge_attachment_vertical);
3295+
3296+ auto const expected_position = on_left_edge();
3297+
3298+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
3299+ basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
3300+ ASSERT_THAT(child.top_left(), Eq(expected_position));
3301+}
3302+
3303+TEST_F(WindowPlacement, given_aux_rect_near_both_sides_edge_attachment_vertical_attaches_to_right_edge)
3304+{
3305+ modification = edge_attachment(rectangle_near_both_sides, mir_edge_attachment_vertical);
3306+
3307+ auto const expected_position = on_right_edge();
3308+
3309+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
3310+ basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
3311+ ASSERT_THAT(child.top_left(), Eq(expected_position));
3312+}
3313+
3314+TEST_F(WindowPlacement, given_aux_rect_away_from_bottom_edge_attachment_horizontal_attaches_to_bottom_edge)
3315+{
3316+ modification = edge_attachment(rectangle_away_from_bottom, mir_edge_attachment_horizontal);
3317+
3318+ auto const expected_position = on_bottom_edge();
3319+
3320+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
3321+ basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
3322+ ASSERT_THAT(child.top_left(), Eq(expected_position));
3323+}
3324+
3325+TEST_F(WindowPlacement, given_aux_rect_near_bottom_edge_attachment_horizontal_attaches_to_top_edge)
3326+{
3327+ modification = edge_attachment(rectangle_near_bottom, mir_edge_attachment_horizontal);
3328+
3329+ auto const expected_position = on_top_edge();
3330+
3331+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
3332+ basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
3333+ ASSERT_THAT(child.top_left(), Eq(expected_position));
3334+}
3335+
3336+TEST_F(WindowPlacement, given_aux_rect_near_both_sides_edge_attachment_any_attaches_to_bottom_edge)
3337+{
3338+ modification = edge_attachment(rectangle_near_both_sides, mir_edge_attachment_any);
3339+
3340+ auto const expected_position = on_bottom_edge();
3341+
3342+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
3343+ basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
3344+ ASSERT_THAT(child.top_left(), Eq(expected_position));
3345+}
3346+
3347+TEST_F(WindowPlacement, given_aux_rect_near_both_sides_and_bottom_edge_attachment_any_attaches_to_top_edge)
3348+{
3349+ modification = edge_attachment(rectangle_near_both_sides_and_bottom, mir_edge_attachment_any);
3350+
3351+ auto const expected_position = on_top_edge();
3352+
3353+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
3354+ basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
3355+ ASSERT_THAT(child.top_left(), Eq(expected_position));
3356+}
3357+
3358+TEST_F(WindowPlacement, given_aux_rect_near_all_sides_attachment_any_attaches_to_right_edge)
3359+{
3360+ modification = edge_attachment(rectangle_near_all_sides, mir_edge_attachment_any);
3361+
3362+ auto const expected_position = on_right_edge();
3363+
3364+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
3365+ basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
3366+ ASSERT_THAT(child.top_left(), Eq(expected_position));
3367+}
3368+
3369+namespace
3370+{
3371+MirPlacementGravity const all_gravities[] =
3372+{
3373+ mir_placement_gravity_northwest,
3374+ mir_placement_gravity_north,
3375+ mir_placement_gravity_northeast,
3376+ mir_placement_gravity_west,
3377+ mir_placement_gravity_center,
3378+ mir_placement_gravity_east,
3379+ mir_placement_gravity_southwest,
3380+ mir_placement_gravity_south,
3381+ mir_placement_gravity_southeast,
3382+};
3383+
3384+auto position_of(MirPlacementGravity rect_gravity, Rectangle rectangle) -> Point
3385+{
3386+ auto const displacement = as_displacement(rectangle.size);
3387+
3388+ switch (rect_gravity)
3389+ {
3390+ case mir_placement_gravity_northwest:
3391+ return rectangle.top_left;
3392+
3393+ case mir_placement_gravity_north:
3394+ return rectangle.top_left + Displacement{0.5 * displacement.dx, 0};
3395+
3396+ case mir_placement_gravity_northeast:
3397+ return rectangle.top_right();
3398+
3399+ case mir_placement_gravity_west:
3400+ return rectangle.top_left + Displacement{0, 0.5 * displacement.dy};
3401+
3402+ case mir_placement_gravity_center:
3403+ return rectangle.top_left + 0.5 * displacement;
3404+
3405+ case mir_placement_gravity_east:
3406+ return rectangle.top_right() + Displacement{0, 0.5 * displacement.dy};
3407+
3408+ case mir_placement_gravity_southwest:
3409+ return rectangle.bottom_left();
3410+
3411+ case mir_placement_gravity_south:
3412+ return rectangle.bottom_left() + Displacement{0.5 * displacement.dx, 0};
3413+
3414+ case mir_placement_gravity_southeast:
3415+ return rectangle.bottom_right();
3416+
3417+ default:
3418+ throw std::runtime_error("bad placement gravity");
3419+ }
3420+
3421+}
3422+}
3423+
3424+TEST_F(WindowPlacement, given_no_hints_can_attach_by_every_gravity)
3425+{
3426+ modification.aux_rect() = Rectangle{{100, 50}, { 20, 20}};
3427+ modification.placement_hints() = MirPlacementHints{};
3428+
3429+ for (auto const rect_gravity : all_gravities)
3430+ {
3431+ modification.aux_rect_placement_gravity() = rect_gravity;
3432+
3433+ auto const rect_anchor = position_of(rect_gravity, aux_rect_position());
3434+
3435+ for (auto const window_gravity : all_gravities)
3436+ {
3437+ modification.window_placement_gravity() = window_gravity;
3438+
3439+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, _));
3440+ basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
3441+
3442+ Rectangle child_rect{child.top_left(), child.size()};
3443+
3444+ EXPECT_THAT(position_of(window_gravity, child_rect), Eq(rect_anchor))
3445+ << "rect_gravity=" << rect_gravity << ", window_gravity=" << window_gravity;
3446+ Mock::VerifyAndClearExpectations(window_manager_policy);
3447+ }
3448+ }
3449+}
3450+
3451+TEST_F(WindowPlacement, given_no_hints_can_attach_by_offset_at_every_gravity)
3452+{
3453+ auto const offset = Displacement{42, 13};
3454+
3455+ modification.aux_rect() = Rectangle{{100, 50}, { 20, 20}};
3456+ modification.placement_hints() = MirPlacementHints{};
3457+ modification.aux_rect_placement_offset() = offset;
3458+
3459+ for (auto const rect_gravity : all_gravities)
3460+ {
3461+ modification.aux_rect_placement_gravity() = rect_gravity;
3462+
3463+ auto const rect_anchor = position_of(rect_gravity, aux_rect_position()) + offset;
3464+
3465+ for (auto const window_gravity : all_gravities)
3466+ {
3467+ modification.window_placement_gravity() = window_gravity;
3468+
3469+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, _));
3470+ basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
3471+
3472+ Rectangle child_rect{child.top_left(), child.size()};
3473+
3474+ EXPECT_THAT(position_of(window_gravity, child_rect), Eq(rect_anchor))
3475+ << "rect_gravity=" << rect_gravity << ", window_gravity=" << window_gravity;
3476+ Mock::VerifyAndClearExpectations(window_manager_policy);
3477+ }
3478+ }
3479+}
3480+
3481+TEST_F(WindowPlacement, given_aux_rect_near_right_side_and_offset_placement_is_flipped)
3482+{
3483+ DeltaX const x_offset{42};
3484+ DeltaY const y_offset{13};
3485+
3486+ modification.aux_rect() = rectangle_near_rhs;
3487+ modification.placement_hints() = mir_placement_hints_flip_x;
3488+ modification.aux_rect_placement_offset() = Displacement{x_offset, y_offset};
3489+ modification.window_placement_gravity() = mir_placement_gravity_northwest;
3490+ modification.aux_rect_placement_gravity() = mir_placement_gravity_northeast;
3491+
3492+ auto const expected_position = on_left_edge() + Displacement{-1*x_offset, y_offset};
3493+
3494+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
3495+ basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
3496+ ASSERT_THAT(child.top_left(), Eq(expected_position));
3497+}
3498+
3499+TEST_F(WindowPlacement, given_aux_rect_near_bottom_and_offset_placement_is_flipped)
3500+{
3501+ DeltaX const x_offset{42};
3502+ DeltaY const y_offset{13};
3503+
3504+ modification.aux_rect() = rectangle_near_bottom;
3505+ modification.placement_hints() = mir_placement_hints_flip_y;
3506+ modification.aux_rect_placement_offset() = Displacement{x_offset, y_offset};
3507+ modification.window_placement_gravity() = mir_placement_gravity_northwest;
3508+ modification.aux_rect_placement_gravity() = mir_placement_gravity_southwest;
3509+
3510+ auto const expected_position = on_top_edge() + Displacement{x_offset, -1*y_offset};
3511+
3512+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
3513+ basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
3514+ ASSERT_THAT(child.top_left(), Eq(expected_position));
3515+}
3516+
3517+TEST_F(WindowPlacement, given_aux_rect_near_bottom_right_and_offset_placement_is_flipped_both_ways)
3518+{
3519+ Displacement const displacement{42, 13};
3520+
3521+ modification.aux_rect() = rectangle_near_both_bottom_right;
3522+ modification.placement_hints() = mir_placement_hints_flip_any;
3523+ modification.aux_rect_placement_offset() = displacement;
3524+ modification.window_placement_gravity() = mir_placement_gravity_northwest;
3525+ modification.aux_rect_placement_gravity() = mir_placement_gravity_southeast;
3526+
3527+ auto const expected_position = aux_rect_position().top_left - as_displacement(child.size()) - displacement;
3528+
3529+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
3530+ basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
3531+ ASSERT_THAT(child.top_left(), Eq(expected_position));
3532+}
3533+
3534+TEST_F(WindowPlacement, given_aux_rect_near_right_side_placement_can_slide_in_x)
3535+{
3536+ modification.aux_rect() = rectangle_near_rhs;
3537+ modification.placement_hints() = mir_placement_hints_slide_x;
3538+ modification.window_placement_gravity() = mir_placement_gravity_northwest;
3539+ modification.aux_rect_placement_gravity() = mir_placement_gravity_northeast;
3540+
3541+ Point const expected_position{display_area.top_right().x - as_displacement(child.size()).dx, aux_rect_position().top_left.y};
3542+
3543+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
3544+ basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
3545+ ASSERT_THAT(child.top_left(), Eq(expected_position));
3546+}
3547+
3548+TEST_F(WindowPlacement, given_aux_rect_near_left_side_placement_can_slide_in_x)
3549+{
3550+ Rectangle const rectangle_near_left_side{{0, 20}, {20, 20}};
3551+
3552+ modification.aux_rect() = rectangle_near_left_side;
3553+ modification.placement_hints() = mir_placement_hints_slide_x;
3554+ modification.window_placement_gravity() = mir_placement_gravity_northeast;
3555+ modification.aux_rect_placement_gravity() = mir_placement_gravity_northwest;
3556+
3557+ Point const expected_position{display_area.top_left.x, aux_rect_position().top_left.y};
3558+
3559+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
3560+ basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
3561+ ASSERT_THAT(child.top_left(), Eq(expected_position));
3562+}
3563+
3564+TEST_F(WindowPlacement, given_aux_rect_near_bottom_placement_can_slide_in_y)
3565+{
3566+ modification.aux_rect() = rectangle_near_bottom;
3567+ modification.placement_hints() = mir_placement_hints_slide_y;
3568+ modification.window_placement_gravity() = mir_placement_gravity_northwest;
3569+ modification.aux_rect_placement_gravity() = mir_placement_gravity_southwest;
3570+
3571+ Point const expected_position{
3572+ aux_rect_position().top_left.x,
3573+ (display_area.bottom_left() - as_displacement(child.size())).y};
3574+
3575+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
3576+ basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
3577+ ASSERT_THAT(child.top_left(), Eq(expected_position));
3578+}
3579+
3580+TEST_F(WindowPlacement, given_aux_rect_near_top_placement_can_slide_in_y)
3581+{
3582+ modification.aux_rect() = rectangle_near_all_sides;
3583+ modification.placement_hints() = mir_placement_hints_slide_y;
3584+ modification.window_placement_gravity() = mir_placement_gravity_southwest;
3585+ modification.aux_rect_placement_gravity() = mir_placement_gravity_northwest;
3586+
3587+ Point const expected_position{aux_rect_position().top_left.x, display_area.top_left.y};
3588+
3589+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
3590+ basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
3591+ ASSERT_THAT(child.top_left(), Eq(expected_position));
3592+}
3593+
3594+TEST_F(WindowPlacement, given_aux_rect_near_bottom_right_and_offset_placement_can_slide_in_x_and_y)
3595+{
3596+ modification.aux_rect() = rectangle_near_both_bottom_right;
3597+ modification.placement_hints() = mir_placement_hints_slide_any;
3598+ modification.window_placement_gravity() = mir_placement_gravity_northwest;
3599+ modification.aux_rect_placement_gravity() = mir_placement_gravity_southwest;
3600+
3601+ auto const expected_position = display_area.bottom_right() - as_displacement(child.size());
3602+
3603+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
3604+ basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
3605+ ASSERT_THAT(child.top_left(), Eq(expected_position));
3606+}
3607+
3608+TEST_F(WindowPlacement, given_aux_rect_near_right_side_placement_can_resize_in_x)
3609+{
3610+ modification.aux_rect() = rectangle_near_rhs;
3611+ modification.placement_hints() = mir_placement_hints_resize_x;
3612+ modification.window_placement_gravity() = mir_placement_gravity_northwest;
3613+ modification.aux_rect_placement_gravity() = mir_placement_gravity_northeast;
3614+
3615+ auto const expected_position = aux_rect_position().top_right();
3616+ Size const expected_size{as_size(display_area.bottom_right()-aux_rect_position().bottom_right()).width, child.size().height};
3617+
3618+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
3619+ EXPECT_CALL(*window_manager_policy, advise_resize(_, expected_size));
3620+ basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
3621+ ASSERT_THAT(child.top_left(), Eq(expected_position));
3622+ ASSERT_THAT(child.size(), Eq(expected_size));
3623+}
3624+
3625+TEST_F(WindowPlacement, given_aux_rect_near_left_side_placement_can_resize_in_x)
3626+{
3627+ Rectangle const rectangle_near_left_side{{0, 20}, {20, 20}};
3628+
3629+ modification.aux_rect() = rectangle_near_left_side;
3630+ modification.placement_hints() = mir_placement_hints_resize_x;
3631+ modification.window_placement_gravity() = mir_placement_gravity_northeast;
3632+ modification.aux_rect_placement_gravity() = mir_placement_gravity_northwest;
3633+
3634+ Point const expected_position{display_area.top_left.x, aux_rect_position().top_left.y};
3635+ Size const expected_size{as_size(aux_rect_position().top_left-display_area.top_left).width, child.size().height};
3636+
3637+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
3638+ EXPECT_CALL(*window_manager_policy, advise_resize(_, expected_size));
3639+ basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
3640+ ASSERT_THAT(child.top_left(), Eq(expected_position));
3641+ ASSERT_THAT(child.size(), Eq(expected_size));
3642+}
3643+
3644+TEST_F(WindowPlacement, given_aux_rect_near_bottom_placement_can_resize_in_y)
3645+{
3646+ modification.aux_rect() = rectangle_near_bottom;
3647+ modification.placement_hints() = mir_placement_hints_resize_y;
3648+ modification.window_placement_gravity() = mir_placement_gravity_northwest;
3649+ modification.aux_rect_placement_gravity() = mir_placement_gravity_southwest;
3650+
3651+ auto const expected_position = aux_rect_position().bottom_left();
3652+ Size const expected_size{child.size().width, as_size(display_area.bottom_left()-aux_rect_position().bottom_left()).height};
3653+
3654+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
3655+ EXPECT_CALL(*window_manager_policy, advise_resize(_, expected_size));
3656+ basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
3657+ ASSERT_THAT(child.top_left(), Eq(expected_position));
3658+ ASSERT_THAT(child.size(), Eq(expected_size));
3659+}
3660+
3661+TEST_F(WindowPlacement, given_aux_rect_near_top_placement_can_resize_in_y)
3662+{
3663+ modification.aux_rect() = rectangle_near_all_sides;
3664+ modification.placement_hints() = mir_placement_hints_resize_y;
3665+ modification.window_placement_gravity() = mir_placement_gravity_southwest;
3666+ modification.aux_rect_placement_gravity() = mir_placement_gravity_northwest;
3667+
3668+ Point const expected_position{aux_rect_position().top_left.x, display_area.top_left.y};
3669+ Size const expected_size{child.size().width, as_size(aux_rect_position().top_left-display_area.top_left).height};
3670+
3671+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
3672+ EXPECT_CALL(*window_manager_policy, advise_resize(_, expected_size));
3673+ basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
3674+ ASSERT_THAT(child.top_left(), Eq(expected_position));
3675+ ASSERT_THAT(child.size(), Eq(expected_size));
3676+}
3677+
3678+TEST_F(WindowPlacement, given_aux_rect_near_bottom_right_and_offset_placement_can_resize_in_x_and_y)
3679+{
3680+ modification.aux_rect() = rectangle_near_both_bottom_right;
3681+ modification.placement_hints() = mir_placement_hints_resize_any;
3682+ modification.window_placement_gravity() = mir_placement_gravity_northwest;
3683+ modification.aux_rect_placement_gravity() = mir_placement_gravity_southeast;
3684+
3685+ auto const expected_position = aux_rect_position().bottom_right();
3686+ auto const expected_size = as_size(display_area.bottom_right()-expected_position);
3687+
3688+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
3689+ EXPECT_CALL(*window_manager_policy, advise_resize(_, expected_size));
3690+ basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
3691+ ASSERT_THAT(child.top_left(), Eq(expected_position));
3692+ ASSERT_THAT(child.size(), Eq(expected_size));
3693+}
3694
3695=== added file 'tests/miral/window_placement_anchors_to_parent.cpp'
3696--- tests/miral/window_placement_anchors_to_parent.cpp 1970-01-01 00:00:00 +0000
3697+++ tests/miral/window_placement_anchors_to_parent.cpp 2017-08-24 15:19:58 +0000
3698@@ -0,0 +1,208 @@
3699+/*
3700+ * Copyright © 2016 Canonical Ltd.
3701+ *
3702+ * This program is free software: you can redistribute it and/or modify it
3703+ * under the terms of the GNU General Public License version 2 or 3 as
3704+ * published by the Free Software Foundation.
3705+ *
3706+ * This program is distributed in the hope that it will be useful,
3707+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3708+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3709+ * GNU General Public License for more details.
3710+ *
3711+ * You should have received a copy of the GNU General Public License
3712+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3713+ *
3714+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
3715+ */
3716+
3717+#include "test_window_manager_tools.h"
3718+
3719+using namespace miral;
3720+using namespace testing;
3721+namespace mt = mir::test;
3722+
3723+namespace
3724+{
3725+auto const display_area = Rectangle{{0, 0}, {800, 600}};
3726+auto const parent_width = 400;
3727+auto const parent_height = 300;
3728+
3729+auto placement(
3730+ Rectangle const& aux_rect,
3731+ MirPlacementGravity aux_rect_placement_gravity,
3732+ MirPlacementGravity window_placement_gravity,
3733+ MirPlacementHints placement_hints) -> WindowSpecification
3734+{
3735+ WindowSpecification modification;
3736+
3737+ modification.aux_rect() = aux_rect;
3738+ modification.aux_rect_placement_gravity() = aux_rect_placement_gravity;
3739+ modification.window_placement_gravity() = window_placement_gravity;
3740+ modification.placement_hints() = placement_hints;
3741+
3742+ return modification;
3743+}
3744+
3745+struct WindowPlacementAnchorsToParent : TestWindowManagerTools
3746+{
3747+ Size const parent_size{parent_width, parent_height};
3748+ Size const initial_child_size{100, 50};
3749+
3750+ Window parent;
3751+ Window child;
3752+
3753+ Point parent_position;
3754+ WindowSpecification modification;
3755+
3756+ void SetUp() override
3757+ {
3758+ TestWindowManagerTools::SetUp();
3759+
3760+ basic_window_manager.add_display_for_testing(display_area);
3761+
3762+ mir::scene::SurfaceCreationParameters creation_parameters;
3763+ basic_window_manager.add_session(session);
3764+
3765+ EXPECT_CALL(*window_manager_policy, advise_new_window(_))
3766+ .WillOnce(Invoke([this](WindowInfo const& window_info){ parent = window_info.window(); }))
3767+ .WillOnce(Invoke([this](WindowInfo const& window_info){ child = window_info.window(); }));
3768+
3769+ creation_parameters.size = parent_size;
3770+ basic_window_manager.add_surface(session, creation_parameters, &create_surface);
3771+
3772+ creation_parameters.type = mir_window_type_tip;
3773+ creation_parameters.parent = parent;
3774+ creation_parameters.size = initial_child_size;
3775+ basic_window_manager.add_surface(session, creation_parameters, &create_surface);
3776+
3777+ // Clear the expectations used to capture parent & child
3778+ Mock::VerifyAndClearExpectations(window_manager_policy);
3779+
3780+ parent_position = parent.top_left();
3781+ }
3782+};
3783+}
3784+
3785+// there was an IRC conversation to sort this out between myself William and Thomas.
3786+// I think the resulting consensus was:
3787+//
3788+// 1. Mir will constrain the placement anchor of the aux_rect to the parent
3789+// surface. I don't think we agreed exactly how (e.g. do we "clip" the
3790+// rect? What happens if there is *no* intersection?)
3791+//
3792+// 2. Mir will constrain the the offset placement anchor to the parent surface.
3793+// Again I don't think we agreed how. (Slide it horizontally and/or vertically
3794+// the minimum amount?)
3795+// - alan_g (mir-devel, Mon, 5 Sep 2016 17:21:01 +0100)
3796+//
3797+// What we have implemented is to constrain the result of offsetting to the parent. That
3798+// seems to provide reasonable behaviour. Are there test cases that require something more?
3799+
3800+TEST_F(WindowPlacementAnchorsToParent, given_rect_anchor_right_of_parent_client_is_anchored_to_parent)
3801+{
3802+ auto const rect_size = 10;
3803+ Rectangle const overlapping_right{{parent_width-rect_size/2, parent_height/2}, {rect_size, rect_size}};
3804+
3805+ modification = placement(
3806+ overlapping_right,
3807+ mir_placement_gravity_northeast,
3808+ mir_placement_gravity_northwest,
3809+ MirPlacementHints(mir_placement_hints_slide_y|mir_placement_hints_resize_x));
3810+
3811+ auto const expected_position = parent_position + Displacement{parent_width, parent_height/2};
3812+
3813+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
3814+ EXPECT_CALL(*window_manager_policy, advise_resize(_, _)).Times(0);
3815+ basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
3816+ ASSERT_THAT(child.top_left(), Eq(expected_position));
3817+ ASSERT_THAT(child.size(), Eq(initial_child_size));
3818+}
3819+
3820+TEST_F(WindowPlacementAnchorsToParent, given_rect_anchor_above_parent_client_is_anchored_to_parent)
3821+{
3822+ auto const rect_size = 10;
3823+ Rectangle const overlapping_above{{parent_width/2, -rect_size/2}, {rect_size, rect_size}};
3824+
3825+ modification = placement(
3826+ overlapping_above,
3827+ mir_placement_gravity_northeast,
3828+ mir_placement_gravity_southeast,
3829+ mir_placement_hints_slide_x);
3830+
3831+ auto const expected_position = parent_position + DeltaX{parent_width/2 + rect_size}
3832+ - as_displacement(initial_child_size);
3833+
3834+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
3835+ EXPECT_CALL(*window_manager_policy, advise_resize(_, _)).Times(0);
3836+ basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
3837+ ASSERT_THAT(child.top_left(), Eq(expected_position));
3838+ ASSERT_THAT(child.size(), Eq(initial_child_size));
3839+}
3840+
3841+TEST_F(WindowPlacementAnchorsToParent, given_offset_right_of_parent_client_is_anchored_to_parent)
3842+{
3843+ auto const rect_size = 10;
3844+ Rectangle const mid_right{{parent_width-rect_size, parent_height/2}, {rect_size, rect_size}};
3845+
3846+ modification = placement(
3847+ mid_right,
3848+ mir_placement_gravity_northeast,
3849+ mir_placement_gravity_northwest,
3850+ MirPlacementHints(mir_placement_hints_slide_y|mir_placement_hints_resize_x));
3851+
3852+ modification.aux_rect_placement_offset() = Displacement{rect_size, 0};
3853+
3854+ auto const expected_position = parent_position + Displacement{parent_width, parent_height/2};
3855+
3856+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
3857+ EXPECT_CALL(*window_manager_policy, advise_resize(_, _)).Times(0);
3858+ basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
3859+ ASSERT_THAT(child.top_left(), Eq(expected_position));
3860+ ASSERT_THAT(child.size(), Eq(initial_child_size));
3861+}
3862+
3863+TEST_F(WindowPlacementAnchorsToParent, given_offset_above_parent_client_is_anchored_to_parent)
3864+{
3865+ auto const rect_size = 10;
3866+ Rectangle const mid_top{{parent_width/2, 0}, {rect_size, rect_size}};
3867+
3868+ modification = placement(
3869+ mid_top,
3870+ mir_placement_gravity_northeast,
3871+ mir_placement_gravity_southeast,
3872+ mir_placement_hints_slide_x);
3873+
3874+ modification.aux_rect_placement_offset() = Displacement{0, -rect_size};
3875+
3876+ auto const expected_position = parent_position + DeltaX{parent_width/2 + rect_size}
3877+ - as_displacement(initial_child_size);
3878+
3879+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
3880+ EXPECT_CALL(*window_manager_policy, advise_resize(_, _)).Times(0);
3881+ basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
3882+ ASSERT_THAT(child.top_left(), Eq(expected_position));
3883+ ASSERT_THAT(child.size(), Eq(initial_child_size));
3884+}
3885+
3886+TEST_F(WindowPlacementAnchorsToParent, given_rect_and_offset_below_left_parent_client_is_anchored_to_parent)
3887+{
3888+ auto const rect_size = 10;
3889+ Rectangle const below_left{{-rect_size, parent_height}, {rect_size, rect_size}};
3890+
3891+ modification = placement(
3892+ below_left,
3893+ mir_placement_gravity_southwest,
3894+ mir_placement_gravity_northeast,
3895+ mir_placement_hints_resize_any);
3896+
3897+ modification.aux_rect_placement_offset() = Displacement{-rect_size, rect_size};
3898+
3899+ auto const expected_position = parent_position + DeltaY{parent_height} - as_displacement(initial_child_size).dx;
3900+
3901+ EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
3902+ EXPECT_CALL(*window_manager_policy, advise_resize(_, _)).Times(0);
3903+ basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
3904+ ASSERT_THAT(child.top_left(), Eq(expected_position));
3905+ ASSERT_THAT(child.size(), Eq(initial_child_size));
3906+}
3907
3908=== added file 'tests/miral/window_placement_client_api.cpp'
3909--- tests/miral/window_placement_client_api.cpp 1970-01-01 00:00:00 +0000
3910+++ tests/miral/window_placement_client_api.cpp 2017-08-24 15:19:58 +0000
3911@@ -0,0 +1,141 @@
3912+/*
3913+ * Copyright © 2016 Canonical Ltd.
3914+ *
3915+ * This program is free software: you can redistribute it and/or modify it
3916+ * under the terms of the GNU General Public License version 2 or 3 as
3917+ * published by the Free Software Foundation.
3918+ *
3919+ * This program is distributed in the hope that it will be useful,
3920+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3921+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3922+ * GNU General Public License for more details.
3923+ *
3924+ * You should have received a copy of the GNU General Public License
3925+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3926+ *
3927+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
3928+ */
3929+
3930+#include <mir_toolkit/events/window_placement.h>
3931+
3932+#include <mir/client/window_spec.h>
3933+#include <mir/client/window.h>
3934+
3935+#include <mir/test/signal.h>
3936+#include "test_server.h"
3937+
3938+#include <gtest/gtest.h>
3939+#include <gmock/gmock.h>
3940+
3941+using namespace std::literals::chrono_literals;
3942+
3943+using namespace testing;
3944+namespace mt = mir::test;
3945+namespace mtf = mir_test_framework;
3946+
3947+using namespace mir::client;
3948+
3949+namespace
3950+{
3951+struct WindowPlacementClientAPI : miral::TestServer
3952+{
3953+ void SetUp() override
3954+ {
3955+ miral::TestServer::SetUp();
3956+
3957+ char const* const test_name = __PRETTY_FUNCTION__;
3958+
3959+ connection = connect_client(test_name);
3960+ auto spec = WindowSpec::for_normal_window(connection, 400, 400)
3961+ .set_name(test_name);
3962+
3963+ parent = spec.create_window();
3964+ }
3965+
3966+ void TearDown() override
3967+ {
3968+ child.reset();
3969+ parent.reset();
3970+ connection.reset();
3971+
3972+ miral::TestServer::TearDown();
3973+ }
3974+
3975+ Connection connection;
3976+ Window parent;
3977+ Window child;
3978+};
3979+}
3980+
3981+namespace
3982+{
3983+struct CheckPlacement
3984+{
3985+ CheckPlacement(int left, int top, unsigned int width, unsigned int height) :
3986+ expected{left, top, width, height} {}
3987+
3988+ void check(MirWindowPlacementEvent const* placement_event)
3989+ {
3990+ auto relative_position = mir_window_placement_get_relative_position(placement_event);
3991+
3992+ EXPECT_THAT(relative_position.top, Eq(expected.top));
3993+ EXPECT_THAT(relative_position.left, Eq(expected.left));
3994+ EXPECT_THAT(relative_position.height, Eq(expected.height));
3995+ EXPECT_THAT(relative_position.width, Eq(expected.width));
3996+
3997+ received.raise();
3998+ }
3999+
4000+ static void callback(MirWindow* /*surface*/, MirEvent const* event, void* context)
4001+ {
4002+ if (mir_event_get_type(event) == mir_event_type_window_placement)
4003+ {
4004+ auto const placement_event = mir_event_get_window_placement_event(event);
4005+ static_cast<CheckPlacement*>(context)->check(placement_event);
4006+ }
4007+ }
4008+
4009+ ~CheckPlacement()
4010+ {
4011+ EXPECT_TRUE(received.wait_for(400ms));
4012+ }
4013+
4014+private:
4015+ MirRectangle const expected;
4016+ mt::Signal received;
4017+};
4018+}
4019+
4020+// It would be nice to verify creation and movement placement notifications in separate tests,
4021+// However, to avoid a racy test, we need to detect both anyway. This seems like a good trade-off.
4022+TEST_F(WindowPlacementClientAPI, given_menu_placements_away_from_edges_when_notified_result_is_as_requested)
4023+{
4024+ char const* const test_name = __PRETTY_FUNCTION__;
4025+ int const dx = 30;
4026+ int const dy = 40;
4027+
4028+ // initial placement
4029+ {
4030+ MirRectangle aux_rect{10, 20, 3, 4};
4031+ CheckPlacement expected{aux_rect.left+(int)aux_rect.width, aux_rect.top, dx, dy};
4032+
4033+ auto const spec = WindowSpec::
4034+ for_menu(connection, dx, dy, parent, &aux_rect, mir_edge_attachment_any)
4035+ .set_event_handler(&CheckPlacement::callback, &expected)
4036+ .set_name(test_name);
4037+
4038+ child = spec.create_window();
4039+ }
4040+
4041+ // subsequent movement
4042+ {
4043+ MirRectangle aux_rect{50, 60, 5, 7};
4044+ CheckPlacement expected{aux_rect.left-dx, aux_rect.top, dx, dy};
4045+
4046+ auto const spec = WindowSpec::for_changes(connection)
4047+ .set_event_handler(&CheckPlacement::callback, &expected)
4048+ .set_placement(&aux_rect, mir_placement_gravity_northwest, mir_placement_gravity_northeast, mir_placement_hints_flip_x, 0, 0);
4049+
4050+ spec.apply_to(child);
4051+ }
4052+}
4053
4054=== added file 'tests/miral/window_properties.cpp'
4055--- tests/miral/window_properties.cpp 1970-01-01 00:00:00 +0000
4056+++ tests/miral/window_properties.cpp 2017-08-24 15:19:58 +0000
4057@@ -0,0 +1,164 @@
4058+/*
4059+ * Copyright © 2017 Canonical Ltd.
4060+ *
4061+ * This program is free software: you can redistribute it and/or modify it
4062+ * under the terms of the GNU General Public License version 2 or 3 as
4063+ * published by the Free Software Foundation.
4064+ *
4065+ * This program is distributed in the hope that it will be useful,
4066+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4067+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4068+ * GNU General Public License for more details.
4069+ *
4070+ * You should have received a copy of the GNU General Public License
4071+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4072+ *
4073+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
4074+ */
4075+
4076+#include <miral/window_manager_tools.h>
4077+
4078+#include <mir/client/surface.h>
4079+#include <mir/client/window.h>
4080+#include <mir/client/window_spec.h>
4081+#include <mir_toolkit/mir_buffer_stream.h>
4082+
4083+#include "test_server.h"
4084+
4085+#include <gmock/gmock.h>
4086+#include <mir/test/signal.h>
4087+
4088+
4089+using namespace testing;
4090+using namespace mir::client;
4091+using namespace std::chrono_literals;
4092+using miral::WindowManagerTools;
4093+
4094+namespace
4095+{
4096+std::string const a_window{"a window"};
4097+
4098+struct WindowProperties;
4099+
4100+struct WindowProperties : public miral::TestServer
4101+{
4102+ void SetUp() override
4103+ {
4104+ miral::TestServer::SetUp();
4105+ client_connection = connect_client("WindowProperties");
4106+ surface = Surface{mir_connection_create_render_surface_sync(client_connection, 40, 40)};
4107+ }
4108+
4109+ void TearDown() override
4110+ {
4111+ surface.reset();
4112+ client_connection.reset();
4113+ miral::TestServer::TearDown();
4114+ }
4115+
4116+ Connection client_connection;
4117+ Surface surface;
4118+
4119+ std::unique_ptr<TestWindowManagerPolicy> build_window_manager_policy(WindowManagerTools const& tools) override;
4120+
4121+ void paint(Surface const& surface)
4122+ {
4123+ mir_buffer_stream_swap_buffers_sync(
4124+ mir_render_surface_get_buffer_stream(surface, 50, 50, mir_pixel_format_argb_8888));
4125+ }
4126+
4127+ mir::test::Signal window_ready;
4128+};
4129+
4130+auto WindowProperties::build_window_manager_policy(WindowManagerTools const& tools)
4131+-> std::unique_ptr<miral::TestServer::TestWindowManagerPolicy>
4132+{
4133+ struct MockWindowManagerPolicy : miral::TestServer::TestWindowManagerPolicy
4134+ {
4135+ using miral::TestServer::TestWindowManagerPolicy::TestWindowManagerPolicy;
4136+ MOCK_METHOD1(advise_focus_gained, void (miral::WindowInfo const& window_info));
4137+ };
4138+
4139+ auto result = std::make_unique<MockWindowManagerPolicy>(tools, *this);
4140+
4141+ ON_CALL(*result, advise_focus_gained(_))
4142+ .WillByDefault(InvokeWithoutArgs([this] { window_ready.raise(); }));
4143+
4144+ return std::move(result);
4145+}
4146+}
4147+
4148+TEST_F(WindowProperties, on_creation_default_shell_chrome_is_normal)
4149+{
4150+ auto const window = WindowSpec::for_normal_window(client_connection, 50, 50)
4151+ .set_name(a_window.c_str())
4152+ .add_surface(surface, 50, 50, 0, 0)
4153+ .create_window();
4154+
4155+ paint(surface);
4156+ ASSERT_TRUE(window_ready.wait_for(400ms));
4157+
4158+ invoke_tools([&, this](WindowManagerTools& tools)
4159+ {
4160+ EXPECT_THAT(tools.info_for(tools.active_window()).shell_chrome(), Eq(mir_shell_chrome_normal));
4161+ });
4162+}
4163+
4164+TEST_F(WindowProperties, on_creation_client_setting_shell_chrome_low_is_seen_by_window_manager)
4165+{
4166+ auto const window = WindowSpec::for_normal_window(client_connection, 50, 50)
4167+ .set_name(a_window.c_str())
4168+ .set_shell_chrome(mir_shell_chrome_low)
4169+ .add_surface(surface, 50, 50, 0, 0)
4170+ .create_window();
4171+
4172+ paint(surface);
4173+ ASSERT_TRUE(window_ready.wait_for(400ms));
4174+
4175+ invoke_tools([&, this](WindowManagerTools& tools)
4176+ {
4177+ EXPECT_THAT(tools.info_for(tools.active_window()).shell_chrome(), Eq(mir_shell_chrome_low));
4178+ });
4179+}
4180+
4181+TEST_F(WindowProperties, after_creation_client_setting_shell_chrome_low_is_seen_by_window_manager)
4182+{
4183+ auto const window = WindowSpec::for_normal_window(client_connection, 50, 50)
4184+ .set_name(a_window.c_str())
4185+ .add_surface(surface, 50, 50, 0, 0)
4186+ .create_window();
4187+
4188+ WindowSpec::for_changes(client_connection)
4189+ .set_shell_chrome(mir_shell_chrome_low)
4190+ .apply_to(window);
4191+
4192+ paint(surface);
4193+
4194+ ASSERT_TRUE(window_ready.wait_for(400ms));
4195+
4196+ invoke_tools([&, this](WindowManagerTools& tools)
4197+ {
4198+ EXPECT_THAT(tools.info_for(tools.active_window()).shell_chrome(), Eq(mir_shell_chrome_low));
4199+ });
4200+}
4201+
4202+TEST_F(WindowProperties, after_creation_client_setting_shell_chrome_normal_is_seen_by_window_manager)
4203+{
4204+ auto const window = WindowSpec::for_normal_window(client_connection, 50, 50)
4205+ .set_name(a_window.c_str())
4206+ .set_shell_chrome(mir_shell_chrome_low)
4207+ .add_surface(surface, 50, 50, 0, 0)
4208+ .create_window();
4209+
4210+ WindowSpec::for_changes(client_connection)
4211+ .set_shell_chrome(mir_shell_chrome_normal)
4212+ .apply_to(window);
4213+
4214+ paint(surface);
4215+ ASSERT_TRUE(window_ready.wait_for(400ms));
4216+
4217+ invoke_tools([&, this](WindowManagerTools& tools)
4218+ {
4219+ EXPECT_THAT(tools.info_for(tools.active_window()).shell_chrome(), Eq(mir_shell_chrome_normal));
4220+ });
4221+}
4222
4223=== added file 'tests/miral/workspaces.cpp'
4224--- tests/miral/workspaces.cpp 1970-01-01 00:00:00 +0000
4225+++ tests/miral/workspaces.cpp 2017-08-24 15:19:58 +0000
4226@@ -0,0 +1,707 @@
4227+/*
4228+ * Copyright © 2017 Canonical Ltd.
4229+ *
4230+ * This program is free software: you can redistribute it and/or modify it
4231+ * under the terms of the GNU General Public License version 2 or 3 as
4232+ * published by the Free Software Foundation.
4233+ *
4234+ * This program is distributed in the hope that it will be useful,
4235+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4236+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4237+ * GNU General Public License for more details.
4238+ *
4239+ * You should have received a copy of the GNU General Public License
4240+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4241+ *
4242+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
4243+ */
4244+
4245+#include <miral/workspace_policy.h>
4246+#include <miral/window_manager_tools.h>
4247+
4248+#include <mir/client/surface.h>
4249+#include <mir/client/window.h>
4250+#include <mir/client/window_spec.h>
4251+#include <mir_toolkit/mir_buffer_stream.h>
4252+
4253+#include "test_server.h"
4254+
4255+#include <gmock/gmock.h>
4256+#include <mir/test/signal.h>
4257+
4258+
4259+using namespace testing;
4260+using namespace mir::client;
4261+using namespace std::chrono_literals;
4262+using miral::WindowManagerTools;
4263+
4264+namespace
4265+{
4266+std::string const top_level{"top level"};
4267+std::string const dialog{"dialog"};
4268+std::string const tip{"tip"};
4269+std::string const a_window{"a window"};
4270+std::string const another_window{"another window"};
4271+
4272+struct Workspaces;
4273+
4274+struct WorkspacesWindowManagerPolicy : miral::TestServer::TestWindowManagerPolicy, miral::WorkspacePolicy
4275+{
4276+ WorkspacesWindowManagerPolicy(WindowManagerTools const& tools, Workspaces& test_fixture);
4277+ ~WorkspacesWindowManagerPolicy();
4278+
4279+ void advise_new_window(miral::WindowInfo const& window_info);
4280+ void handle_window_ready(miral::WindowInfo& window_info);
4281+
4282+ MOCK_METHOD2(advise_adding_to_workspace,
4283+ void(std::shared_ptr<miral::Workspace> const&, std::vector<miral::Window> const&));
4284+
4285+ MOCK_METHOD2(advise_removing_from_workspace,
4286+ void(std::shared_ptr<miral::Workspace> const&, std::vector<miral::Window> const&));
4287+
4288+ MOCK_METHOD1(advise_focus_gained, void(miral::WindowInfo const&));
4289+
4290+ MOCK_METHOD1(advise_window_ready, void(miral::WindowInfo const&));
4291+
4292+ Workspaces& test_fixture;
4293+};
4294+
4295+struct TestWindow : Surface, Window
4296+{
4297+ using Surface::operator=;
4298+ using Window::operator=;
4299+};
4300+
4301+struct Workspaces : public miral::TestServer
4302+{
4303+ auto create_window(std::string const& name) -> TestWindow
4304+ {
4305+ TestWindow result;
4306+
4307+ result = Surface{mir_connection_create_render_surface_sync(client_connection, 50, 50)};
4308+ result = WindowSpec::for_normal_window(client_connection, 50, 50)
4309+ .set_name(name.c_str())
4310+ .add_surface(result, 50, 50, 0, 0)
4311+ .create_window();
4312+
4313+ client_windows[name] = result;
4314+ init_window(result);
4315+
4316+ return result;
4317+ }
4318+
4319+ void init_window(TestWindow const& window)
4320+ {
4321+ mir::test::Signal window_ready;
4322+ EXPECT_CALL(policy(), advise_window_ready(_)).WillOnce(InvokeWithoutArgs([&]{ window_ready.raise(); }));
4323+
4324+ mir_buffer_stream_swap_buffers_sync(
4325+ mir_render_surface_get_buffer_stream(window, 50, 50, mir_pixel_format_argb_8888));
4326+
4327+ EXPECT_TRUE(window_ready.wait_for(1s));
4328+ }
4329+
4330+ auto create_tip(std::string const& name, Window const& parent) -> TestWindow
4331+ {
4332+ TestWindow result;
4333+
4334+ result = Surface{mir_connection_create_render_surface_sync(client_connection, 50, 50)};
4335+
4336+ MirRectangle aux_rect{10, 10, 10, 10};
4337+ result = WindowSpec::for_tip(client_connection, 50, 50, parent, &aux_rect, mir_edge_attachment_any)
4338+ .set_name(name.c_str())
4339+ .add_surface(result, 50, 50, 0, 0)
4340+ .create_window();
4341+
4342+ client_windows[name] = result;
4343+ init_window(result);
4344+
4345+ return result;
4346+ }
4347+
4348+ auto create_dialog(std::string const& name, Window const& parent) -> TestWindow
4349+ {
4350+ TestWindow result;
4351+
4352+ result = Surface{mir_connection_create_render_surface_sync(client_connection, 50, 50)};
4353+
4354+ result = WindowSpec::for_dialog(client_connection, 50, 50, parent)
4355+ .set_name(name.c_str())
4356+ .add_surface(result, 50, 50, 0, 0)
4357+ .create_window();
4358+
4359+ client_windows[name] = result;
4360+ init_window(result);
4361+
4362+ return result;
4363+ }
4364+
4365+ auto create_workspace() -> std::shared_ptr<miral::Workspace>
4366+ {
4367+ std::shared_ptr<miral::Workspace> result;
4368+
4369+ invoke_tools([&](WindowManagerTools& tools)
4370+ { result = tools.create_workspace(); });
4371+
4372+ return result;
4373+ }
4374+
4375+ void SetUp() override
4376+ {
4377+ miral::TestServer::SetUp();
4378+ EXPECT_CALL(policy(), advise_adding_to_workspace(_, _)).Times(AnyNumber());
4379+ EXPECT_CALL(policy(), advise_removing_from_workspace(_, _)).Times(AnyNumber());
4380+ EXPECT_CALL(policy(), advise_focus_gained(_)).Times(AnyNumber());
4381+
4382+ client_connection = connect_client("Workspaces");
4383+ create_window(top_level);
4384+ create_dialog(dialog, client_windows[top_level]);
4385+ create_tip(tip, client_windows[dialog]);
4386+
4387+ EXPECT_THAT(client_windows.size(), Eq(3u));
4388+ EXPECT_THAT(server_windows.size(), Eq(3u));
4389+ }
4390+
4391+ void TearDown() override
4392+ {
4393+ client_windows.clear();
4394+ client_connection.reset();
4395+ miral::TestServer::TearDown();
4396+ }
4397+
4398+ Connection client_connection;
4399+
4400+ auto server_window(std::string const& key) -> miral::Window
4401+ {
4402+ std::lock_guard<decltype(mutex)> lock{mutex};
4403+ return server_windows[key];
4404+ }
4405+
4406+ auto client_window(std::string const& key) -> Window&
4407+ {
4408+ return client_windows[key];
4409+ }
4410+
4411+ auto windows_in_workspace(std::shared_ptr<miral::Workspace> const& workspace) -> std::vector<miral::Window>
4412+ {
4413+ std::vector<miral::Window> result;
4414+
4415+ auto enumerate = [&result](miral::Window const& window)
4416+ {
4417+ result.push_back(window);
4418+ };
4419+
4420+ invoke_tools([&](WindowManagerTools& tools)
4421+ { tools.for_each_window_in_workspace(workspace, enumerate); });
4422+
4423+ return result;
4424+ }
4425+
4426+ auto workspaces_containing_window(miral::Window const& window) -> std::vector<std::shared_ptr<miral::Workspace>>
4427+ {
4428+ std::vector<std::shared_ptr<miral::Workspace>> result;
4429+
4430+ auto enumerate = [&result](std::shared_ptr<miral::Workspace> const& workspace)
4431+ {
4432+ result.push_back(workspace);
4433+ };
4434+
4435+ invoke_tools([&](WindowManagerTools& tools)
4436+ { tools.for_each_workspace_containing(window, enumerate); });
4437+
4438+ return result;
4439+ }
4440+
4441+ auto policy() -> WorkspacesWindowManagerPolicy&
4442+ {
4443+ if (!the_policy) throw std::logic_error("the_policy isn't valid");
4444+ return *the_policy;
4445+ }
4446+
4447+private:
4448+ std::mutex mutable mutex;
4449+ std::map<std::string, TestWindow> client_windows;
4450+ std::map<std::string, miral::Window> server_windows;
4451+ WorkspacesWindowManagerPolicy* the_policy{nullptr};
4452+
4453+ friend struct WorkspacesWindowManagerPolicy;
4454+
4455+ auto build_window_manager_policy(WindowManagerTools const& tools)
4456+ -> std::unique_ptr<TestWindowManagerPolicy> override
4457+ {
4458+ return std::make_unique<WorkspacesWindowManagerPolicy>(tools, *this);
4459+ }
4460+};
4461+
4462+WorkspacesWindowManagerPolicy::WorkspacesWindowManagerPolicy(WindowManagerTools const& tools, Workspaces& test_fixture) :
4463+TestWindowManagerPolicy(tools, test_fixture), test_fixture{test_fixture}
4464+{
4465+ test_fixture.the_policy = this;
4466+}
4467+
4468+WorkspacesWindowManagerPolicy::~WorkspacesWindowManagerPolicy()
4469+{
4470+ test_fixture.the_policy = nullptr;
4471+}
4472+
4473+
4474+void WorkspacesWindowManagerPolicy::advise_new_window(miral::WindowInfo const& window_info)
4475+{
4476+ miral::TestServer::TestWindowManagerPolicy::advise_new_window(window_info);
4477+
4478+ std::lock_guard<decltype(test_fixture.mutex)> lock{test_fixture.mutex};
4479+ test_fixture.server_windows[window_info.name()] = window_info.window();
4480+}
4481+
4482+void WorkspacesWindowManagerPolicy::handle_window_ready(miral::WindowInfo& window_info)
4483+{
4484+ miral::CanonicalWindowManagerPolicy::handle_window_ready(window_info);
4485+ advise_window_ready(window_info);
4486+}
4487+}
4488+
4489+TEST_F(Workspaces, before_a_tree_is_added_to_workspace_it_is_empty)
4490+{
4491+ auto const workspace = create_workspace();
4492+
4493+ EXPECT_THAT(windows_in_workspace(workspace).size(), Eq(0u));
4494+}
4495+
4496+TEST_F(Workspaces, when_a_tree_is_added_to_workspace_all_surfaces_in_tree_are_added)
4497+{
4498+ auto const workspace = create_workspace();
4499+ invoke_tools([&, this](WindowManagerTools& tools)
4500+ { tools.add_tree_to_workspace(server_window(dialog), workspace); });
4501+
4502+ EXPECT_THAT(windows_in_workspace(workspace).size(), Eq(3u));
4503+}
4504+
4505+TEST_F(Workspaces, when_a_tree_is_removed_from_workspace_all_surfaces_in_tree_are_removed)
4506+{
4507+ auto const workspace = create_workspace();
4508+ invoke_tools([&, this](WindowManagerTools& tools)
4509+ { tools.add_tree_to_workspace(server_window(dialog), workspace); });
4510+
4511+ invoke_tools([&, this](WindowManagerTools& tools)
4512+ { tools.remove_tree_from_workspace(server_window(tip), workspace); });
4513+
4514+ EXPECT_THAT(windows_in_workspace(workspace).size(), Eq(0u));
4515+}
4516+
4517+TEST_F(Workspaces, given_a_tree_in_a_workspace_when_another_tree_is_added_and_removed_from_workspace_the_original_tree_remains)
4518+{
4519+ auto const workspace = create_workspace();
4520+ auto const original_tree = "original_tree";
4521+ auto const client_window = create_window(original_tree);
4522+ auto const original_window= server_window(original_tree);
4523+
4524+ invoke_tools([&, this](WindowManagerTools& tools)
4525+ { tools.add_tree_to_workspace(original_window, workspace); });
4526+
4527+ invoke_tools([&, this](WindowManagerTools& tools)
4528+ { tools.add_tree_to_workspace(server_window(top_level), workspace); });
4529+ invoke_tools([&, this](WindowManagerTools& tools)
4530+ { tools.remove_tree_from_workspace(server_window(top_level), workspace); });
4531+
4532+ EXPECT_THAT(windows_in_workspace(workspace), ElementsAre(original_window));
4533+}
4534+
4535+TEST_F(Workspaces, when_a_tree_is_added_to_a_workspace_all_surfaces_are_contained_in_the_workspace)
4536+{
4537+ auto const workspace = create_workspace();
4538+ invoke_tools([&, this](WindowManagerTools& tools)
4539+ { tools.add_tree_to_workspace(server_window(dialog), workspace); });
4540+
4541+ EXPECT_THAT(workspaces_containing_window(server_window(top_level)), ElementsAre(workspace));
4542+ EXPECT_THAT(workspaces_containing_window(server_window(dialog)), ElementsAre(workspace));
4543+ EXPECT_THAT(workspaces_containing_window(server_window(tip)), ElementsAre(workspace));
4544+}
4545+
4546+
4547+TEST_F(Workspaces, when_a_tree_is_added_to_a_workspaces_twice_surfaces_are_contained_in_one_workspace)
4548+{
4549+ auto const workspace = create_workspace();
4550+ invoke_tools([&, this](WindowManagerTools& tools)
4551+ {
4552+ tools.add_tree_to_workspace(server_window(dialog), workspace);
4553+ tools.add_tree_to_workspace(server_window(dialog), workspace);
4554+ });
4555+
4556+ EXPECT_THAT(workspaces_containing_window(server_window(top_level)), ElementsAre(workspace));
4557+ EXPECT_THAT(workspaces_containing_window(server_window(dialog)), ElementsAre(workspace));
4558+ EXPECT_THAT(workspaces_containing_window(server_window(tip)), ElementsAre(workspace));
4559+
4560+ EXPECT_THAT(workspaces_containing_window(server_window(top_level)).size(), Eq(1u));
4561+ EXPECT_THAT(workspaces_containing_window(server_window(dialog)).size(), Eq(1u));
4562+ EXPECT_THAT(workspaces_containing_window(server_window(tip)).size(), Eq(1u));
4563+}
4564+
4565+TEST_F(Workspaces, when_a_tree_is_added_to_two_workspaces_all_surfaces_are_contained_in_two_workspaces)
4566+{
4567+ auto const workspace1 = create_workspace();
4568+ auto const workspace2 = create_workspace();
4569+ invoke_tools([&, this](WindowManagerTools& tools)
4570+ {
4571+ tools.add_tree_to_workspace(server_window(dialog), workspace1);
4572+ tools.add_tree_to_workspace(server_window(dialog), workspace2);
4573+ });
4574+
4575+ EXPECT_THAT(workspaces_containing_window(server_window(top_level)).size(), Eq(2u));
4576+ EXPECT_THAT(workspaces_containing_window(server_window(dialog)).size(), Eq(2u));
4577+ EXPECT_THAT(workspaces_containing_window(server_window(tip)).size(), Eq(2u));
4578+}
4579+
4580+TEST_F(Workspaces, when_workspace_is_closed_surfaces_are_no_longer_contained_in_it)
4581+{
4582+ auto const workspace1 = create_workspace();
4583+ auto workspace2 = create_workspace();
4584+ invoke_tools([&, this](WindowManagerTools& tools)
4585+ {
4586+ tools.add_tree_to_workspace(server_window(dialog), workspace1);
4587+ tools.add_tree_to_workspace(server_window(dialog), workspace2);
4588+ });
4589+
4590+ workspace2.reset();
4591+
4592+ EXPECT_THAT(workspaces_containing_window(server_window(top_level)), ElementsAre(workspace1));
4593+ EXPECT_THAT(workspaces_containing_window(server_window(dialog)), ElementsAre(workspace1));
4594+ EXPECT_THAT(workspaces_containing_window(server_window(tip)), ElementsAre(workspace1));
4595+
4596+ EXPECT_THAT(workspaces_containing_window(server_window(top_level)).size(), Eq(1u));
4597+ EXPECT_THAT(workspaces_containing_window(server_window(dialog)).size(), Eq(1u));
4598+ EXPECT_THAT(workspaces_containing_window(server_window(tip)).size(), Eq(1u));
4599+}
4600+
4601+TEST_F(Workspaces, when_a_tree_is_added_to_a_workspace_the_policy_is_notified)
4602+{
4603+ auto const workspace = create_workspace();
4604+
4605+ EXPECT_CALL(policy(), advise_adding_to_workspace(workspace,
4606+ ElementsAre(server_window(top_level), server_window(dialog), server_window(tip))));
4607+
4608+ invoke_tools([&, this](WindowManagerTools& tools)
4609+ { tools.add_tree_to_workspace(server_window(dialog), workspace); });
4610+}
4611+
4612+TEST_F(Workspaces, when_a_tree_is_added_to_a_workspaces_twice_the_policy_is_notified_once)
4613+{
4614+ auto const workspace = create_workspace();
4615+
4616+ EXPECT_CALL(policy(), advise_adding_to_workspace(workspace,
4617+ ElementsAre(server_window(top_level), server_window(dialog), server_window(tip))));
4618+
4619+ invoke_tools([&, this](WindowManagerTools& tools)
4620+ {
4621+ tools.add_tree_to_workspace(server_window(dialog), workspace);
4622+ tools.add_tree_to_workspace(server_window(dialog), workspace);
4623+ });
4624+}
4625+
4626+TEST_F(Workspaces, when_a_tree_is_removed_from_a_workspace_the_policy_is_notified)
4627+{
4628+ auto const workspace = create_workspace();
4629+ invoke_tools([&, this](WindowManagerTools& tools)
4630+ { tools.add_tree_to_workspace(server_window(dialog), workspace); });
4631+
4632+ EXPECT_CALL(policy(), advise_removing_from_workspace(workspace,
4633+ ElementsAre(server_window(top_level), server_window(dialog), server_window(tip))));
4634+
4635+ invoke_tools([&, this](WindowManagerTools& tools)
4636+ { tools.remove_tree_from_workspace(server_window(tip), workspace); });
4637+}
4638+
4639+TEST_F(Workspaces, when_a_tree_is_removed_from_a_workspace_twice_the_policy_is_notified_once)
4640+{
4641+ auto const workspace = create_workspace();
4642+ invoke_tools([&, this](WindowManagerTools& tools)
4643+ { tools.add_tree_to_workspace(server_window(dialog), workspace); });
4644+
4645+ EXPECT_CALL(policy(), advise_removing_from_workspace(workspace,
4646+ ElementsAre(server_window(top_level), server_window(dialog), server_window(tip))));
4647+
4648+ invoke_tools([&, this](WindowManagerTools& tools)
4649+ {
4650+ tools.remove_tree_from_workspace(server_window(top_level), workspace);
4651+ tools.remove_tree_from_workspace(server_window(tip), workspace);
4652+ });
4653+}
4654+
4655+TEST_F(Workspaces, a_child_window_is_added_to_workspace_of_parent)
4656+{
4657+ auto const workspace = create_workspace();
4658+ invoke_tools([&, this](WindowManagerTools& tools)
4659+ { tools.add_tree_to_workspace(server_window(dialog), workspace); });
4660+
4661+ EXPECT_CALL(policy(), advise_adding_to_workspace(workspace, ElementsAre(_)));
4662+
4663+ create_dialog(a_window, client_window(top_level));
4664+}
4665+
4666+TEST_F(Workspaces, a_closing_window_is_removed_from_workspace)
4667+{
4668+ auto const workspace = create_workspace();
4669+ invoke_tools([&, this](WindowManagerTools& tools)
4670+ { tools.add_tree_to_workspace(server_window(dialog), workspace); });
4671+
4672+ create_dialog(a_window, client_window(dialog));
4673+
4674+ EXPECT_CALL(policy(), advise_removing_from_workspace(workspace, ElementsAre(server_window(a_window))));
4675+
4676+ client_window(a_window).reset();
4677+}
4678+
4679+TEST_F(Workspaces, when_a_window_in_a_workspace_closes_focus_remains_in_workspace)
4680+{
4681+ auto const workspace = create_workspace();
4682+
4683+ create_window(a_window);
4684+ create_window(another_window);
4685+
4686+ invoke_tools([&, this](WindowManagerTools& tools)
4687+ {
4688+ tools.add_tree_to_workspace(server_window(a_window), workspace);
4689+ tools.add_tree_to_workspace(server_window(another_window), workspace);
4690+
4691+ tools.select_active_window(server_window(dialog));
4692+ tools.select_active_window(server_window(a_window));
4693+ });
4694+
4695+ client_window(a_window).reset();
4696+
4697+ invoke_tools([&, this](WindowManagerTools& tools)
4698+ {
4699+ EXPECT_THAT(tools.active_window(), Eq(server_window(another_window)))
4700+ << "tools.active_window() . . . .: " << tools.info_for(tools.active_window()).name() << "\n"
4701+ << "server_window(another_window): " << tools.info_for(server_window(another_window)).name();
4702+ });
4703+}
4704+
4705+TEST_F(Workspaces, with_two_applications_when_a_window_in_a_workspace_closes_focus_remains_in_workspace)
4706+{
4707+ auto const workspace = create_workspace();
4708+
4709+ create_window(another_window);
4710+
4711+ {
4712+ auto const another_app = connect_client("another app");
4713+ TestWindow window;
4714+ window = Surface{mir_connection_create_render_surface_sync(another_app, 50, 50)};
4715+ window = WindowSpec::for_normal_window(another_app, 50, 50)
4716+ .set_name(a_window.c_str())
4717+ .add_surface(window, 50, 50, 0, 0)
4718+ .create_window();
4719+
4720+ init_window(window);
4721+
4722+ invoke_tools([&, this](WindowManagerTools& tools)
4723+ {
4724+ tools.add_tree_to_workspace(server_window(top_level), workspace);
4725+ tools.add_tree_to_workspace(server_window(a_window), workspace);
4726+ });
4727+ }
4728+
4729+ invoke_tools([&, this](WindowManagerTools& tools)
4730+ {
4731+ EXPECT_THAT(tools.active_window(), Eq(server_window(dialog)))
4732+ << "tools.active_window(): " << tools.info_for(tools.active_window()).name() << "\n"
4733+ << "server_window(dialog): " << tools.info_for(server_window(dialog)).name();
4734+ });
4735+}
4736+
4737+TEST_F(Workspaces, when_a_window_in_a_workspace_hides_focus_remains_in_workspace)
4738+{
4739+ auto const workspace = create_workspace();
4740+
4741+ create_window(a_window);
4742+ create_window(another_window);
4743+
4744+ invoke_tools([&, this](WindowManagerTools& tools)
4745+ {
4746+ tools.add_tree_to_workspace(server_window(a_window), workspace);
4747+ tools.add_tree_to_workspace(server_window(another_window), workspace);
4748+
4749+ tools.select_active_window(server_window(dialog));
4750+ tools.select_active_window(server_window(a_window));
4751+ });
4752+
4753+ mir::test::Signal focus_changed;
4754+ EXPECT_CALL(policy(), advise_focus_gained(_)).WillOnce(InvokeWithoutArgs([&]{ focus_changed.raise(); }));
4755+
4756+ mir_window_set_state(client_window(a_window), mir_window_state_hidden);
4757+
4758+ EXPECT_TRUE(focus_changed.wait_for(1s));
4759+
4760+ invoke_tools([&, this](WindowManagerTools& tools)
4761+ {
4762+ EXPECT_THAT(tools.active_window(), Eq(server_window(another_window)))
4763+ << "tools.active_window() . . . .: " << tools.info_for(tools.active_window()).name() << "\n"
4764+ << "server_window(another_window): " << tools.info_for(server_window(another_window)).name();
4765+ });
4766+}
4767+
4768+
4769+TEST_F(Workspaces, with_two_applications_when_a_window_in_a_workspace_hides_focus_remains_in_workspace)
4770+{
4771+ auto const workspace = create_workspace();
4772+
4773+ create_window(another_window);
4774+
4775+ auto const another_app = connect_client("another app");
4776+ TestWindow window;
4777+ window = Surface{mir_connection_create_render_surface_sync(another_app, 50, 50)};
4778+ window = WindowSpec::for_normal_window(another_app, 50, 50)
4779+ .set_name(a_window.c_str())
4780+ .add_surface(window, 50, 50, 0, 0)
4781+ .create_window();
4782+
4783+ init_window(window);
4784+
4785+ invoke_tools([&, this](WindowManagerTools& tools)
4786+ {
4787+ tools.add_tree_to_workspace(server_window(top_level), workspace);
4788+ tools.add_tree_to_workspace(server_window(a_window), workspace);
4789+ });
4790+
4791+
4792+ mir::test::Signal focus_changed;
4793+ EXPECT_CALL(policy(), advise_focus_gained(_)).WillOnce(InvokeWithoutArgs([&]{ focus_changed.raise(); }));
4794+
4795+ mir_window_set_state(window, mir_window_state_hidden);
4796+
4797+ EXPECT_TRUE(focus_changed.wait_for(1s));
4798+
4799+ invoke_tools([&, this](WindowManagerTools& tools)
4800+ {
4801+ EXPECT_THAT(tools.active_window(), Eq(server_window(dialog)))
4802+ << "tools.active_window(): " << tools.info_for(tools.active_window()).name() << "\n"
4803+ << "server_window(dialog): " << tools.info_for(server_window(dialog)).name();
4804+ });
4805+
4806+ Mock::VerifyAndClearExpectations(&policy()); // before shutdown
4807+}
4808+
4809+TEST_F(Workspaces, focus_next_within_application_keeps_focus_in_workspace)
4810+{
4811+ auto const workspace = create_workspace();
4812+
4813+ create_window(another_window);
4814+ create_window(a_window);
4815+
4816+ invoke_tools([&, this](WindowManagerTools& tools)
4817+ {
4818+ tools.add_tree_to_workspace(server_window(a_window), workspace);
4819+ tools.add_tree_to_workspace(server_window(dialog), workspace);
4820+
4821+ tools.focus_next_within_application();
4822+
4823+ EXPECT_THAT(tools.active_window(), Eq(server_window(dialog)))
4824+ << "tools.active_window(): " << tools.info_for(tools.active_window()).name() << "\n"
4825+ << "server_window(dialog): " << tools.info_for(server_window(dialog)).name();
4826+
4827+ tools.focus_next_within_application();
4828+
4829+ EXPECT_THAT(tools.active_window(), Eq(server_window(a_window)))
4830+ << "tools.active_window(). : " << tools.info_for(tools.active_window()).name() << "\n"
4831+ << "server_window(a_window): " << tools.info_for(server_window(a_window)).name();
4832+ });
4833+}
4834+
4835+TEST_F(Workspaces, focus_next_application_keeps_focus_in_workspace)
4836+{
4837+ auto const workspace = create_workspace();
4838+ create_window(another_window);
4839+
4840+ auto const another_app = connect_client("another app");
4841+ TestWindow window;
4842+ window = Surface{mir_connection_create_render_surface_sync(another_app, 50, 50)};
4843+ window = WindowSpec::for_normal_window(another_app, 50, 50)
4844+ .set_name(a_window.c_str())
4845+ .add_surface(window, 50, 50, 0, 0)
4846+ .create_window();
4847+
4848+ init_window(window);
4849+
4850+ invoke_tools([&, this](WindowManagerTools& tools)
4851+ {
4852+ tools.add_tree_to_workspace(server_window(top_level), workspace);
4853+ tools.add_tree_to_workspace(server_window(a_window), workspace);
4854+
4855+ tools.focus_next_application();
4856+
4857+ EXPECT_THAT(tools.active_window(), Eq(server_window(dialog)))
4858+ << "tools.active_window(): " << tools.info_for(tools.active_window()).name() << "\n"
4859+ << "server_window(dialog): " << tools.info_for(server_window(dialog)).name();
4860+
4861+ tools.focus_next_application();
4862+
4863+ EXPECT_THAT(tools.active_window(), Eq(server_window(a_window)))
4864+ << "tools.active_window(). : " << tools.info_for(tools.active_window()).name() << "\n"
4865+ << "server_window(a_window): " << tools.info_for(server_window(a_window)).name();
4866+ });
4867+}
4868+
4869+TEST_F(Workspaces, move_windows_from_one_workspace_to_another)
4870+{
4871+ auto const pre_workspace = create_workspace();
4872+ auto const from_workspace = create_workspace();
4873+ auto const to_workspace = create_workspace();
4874+ auto const post_workspace = create_workspace();
4875+
4876+ create_window(a_window);
4877+ create_window(another_window);
4878+
4879+ invoke_tools([&, this](WindowManagerTools& tools)
4880+ {
4881+ tools.add_tree_to_workspace(server_window(a_window), pre_workspace);
4882+ tools.add_tree_to_workspace(server_window(top_level), from_workspace);
4883+ tools.add_tree_to_workspace(server_window(another_window), post_workspace);
4884+
4885+ tools.move_workspace_content_to_workspace(to_workspace, from_workspace);
4886+ });
4887+
4888+ EXPECT_THAT(windows_in_workspace(from_workspace).size(), Eq(0u));
4889+
4890+ EXPECT_THAT(workspaces_containing_window(server_window(a_window)), ElementsAre(pre_workspace));
4891+ EXPECT_THAT(workspaces_containing_window(server_window(top_level)), ElementsAre(to_workspace));
4892+ EXPECT_THAT(workspaces_containing_window(server_window(dialog)), ElementsAre(to_workspace));
4893+ EXPECT_THAT(workspaces_containing_window(server_window(tip)), ElementsAre(to_workspace));
4894+ EXPECT_THAT(workspaces_containing_window(server_window(another_window)), ElementsAre(post_workspace));
4895+}
4896+
4897+TEST_F(Workspaces, when_moving_windows_from_one_workspace_to_another_windows_only_appear_once_in_target_workspace)
4898+{
4899+ auto const from_workspace = create_workspace();
4900+ auto const to_workspace = create_workspace();
4901+
4902+ create_window(a_window);
4903+ create_window(another_window);
4904+
4905+ invoke_tools([&, this](WindowManagerTools& tools)
4906+ {
4907+ tools.add_tree_to_workspace(server_window(a_window), from_workspace);
4908+ tools.add_tree_to_workspace(server_window(another_window), from_workspace);
4909+ tools.add_tree_to_workspace(server_window(a_window), to_workspace);
4910+
4911+ tools.move_workspace_content_to_workspace(to_workspace, from_workspace);
4912+ });
4913+
4914+ EXPECT_THAT(windows_in_workspace(to_workspace), ElementsAre(server_window(a_window), server_window(another_window)));
4915+}
4916+
4917+TEST_F(Workspaces, when_workspace_content_is_moved_the_policy_is_notified)
4918+{
4919+ auto const from_workspace = create_workspace();
4920+ auto const to_workspace = create_workspace();
4921+
4922+ EXPECT_CALL(policy(), advise_removing_from_workspace(from_workspace,
4923+ ElementsAre(server_window(top_level), server_window(dialog), server_window(tip))));
4924+
4925+ EXPECT_CALL(policy(), advise_adding_to_workspace(to_workspace,
4926+ ElementsAre(server_window(top_level), server_window(dialog), server_window(tip))));
4927+
4928+ invoke_tools([&, this](WindowManagerTools& tools)
4929+ {
4930+ tools.add_tree_to_workspace(server_window(dialog), from_workspace);
4931+ tools.move_workspace_content_to_workspace(to_workspace, from_workspace);
4932+ });
4933+}

Subscribers

People subscribed via source and target branches