Merge lp:~alan-griffiths/miral/workspaces into lp:miral

Proposed by Alan Griffiths on 2017-02-07
Status: Superseded
Proposed branch: lp:~alan-griffiths/miral/workspaces
Merge into: lp:miral
Diff against target: 2053 lines (+1434/-78)
22 files modified
CMakeLists.txt (+1/-1)
debian/changelog (+31/-4)
debian/libmiral2.symbols (+14/-3)
include/mir/client/window_id.h (+1/-1)
include/miral/window.h (+5/-0)
include/miral/window_manager_tools.h (+55/-1)
include/miral/workspace_policy.h (+88/-0)
miral/CMakeLists.txt (+1/-0)
miral/basic_window_manager.cpp (+405/-50)
miral/basic_window_manager.h (+41/-1)
miral/symbols.map (+28/-0)
miral/window.cpp (+5/-0)
miral/window_management_trace.cpp (+39/-0)
miral/window_management_trace.h (+13/-0)
miral/window_manager_tools.cpp (+23/-0)
miral/window_manager_tools_implementation.h (+11/-0)
miral/workspace_policy.cpp (+29/-0)
scripts/process_doxygen_xml.py (+14/-1)
test/CMakeLists.txt (+1/-1)
test/test_server.cpp (+13/-14)
test/test_server.h (+13/-1)
test/workspaces.cpp (+603/-0)
To merge this branch: bzr merge lp:~alan-griffiths/miral/workspaces
Reviewer Review Type Date Requested Status
Mir development team 2017-02-07 Pending
Review via email: mp+316584@code.launchpad.net

This proposal has been superseded by a proposal from 2017-02-15.

Commit Message

[libmiral] Support for shells implementing workspaces

To post a comment you must log in.
lp:~alan-griffiths/miral/workspaces updated on 2017-02-15
536. By Alan Griffiths on 2017-02-13

merge :parent

537. By Alan Griffiths on 2017-02-14

merge :parent

538. By Alan Griffiths on 2017-02-14

Tidy up code

539. By Alan Griffiths on 2017-02-15

Backport fixes from workspaces-example branch

540. By Alan Griffiths on 2017-02-15

merge lp:miral

541. By Alan Griffiths on 2017-02-15

merge lp:~alan-griffiths/miral/1.2

542. By Alan Griffiths on 2017-02-15

Housekeeping for 1.3 release

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2017-02-02 17:30:41 +0000
3+++ CMakeLists.txt 2017-02-15 17:24:17 +0000
4@@ -41,7 +41,7 @@
5 include_directories(include SYSTEM ${MIRCLIENT_INCLUDE_DIRS})
6
7 set(MIRAL_VERSION_MAJOR 1)
8-set(MIRAL_VERSION_MINOR 2)
9+set(MIRAL_VERSION_MINOR 3)
10 set(MIRAL_VERSION_PATCH 0)
11
12 set(MIRAL_VERSION ${MIRAL_VERSION_MAJOR}.${MIRAL_VERSION_MINOR}.${MIRAL_VERSION_PATCH})
13
14=== modified file 'debian/changelog'
15--- debian/changelog 2017-02-14 14:04:39 +0000
16+++ debian/changelog 2017-02-15 17:24:17 +0000
17@@ -1,6 +1,36 @@
18+miral (1.3.0) UNRELEASED; urgency=medium
19+
20+ * New upstream release 1.3.0 (https://launchpad.net/miral/+milestone/1.3)
21+
22+ - ABI summary:
23+ . miral ABI unchanged at 2
24+ - Enhancements:
25+ . Support for workspaces
26+ - Bugs fixed:
27+
28+ -- Alan Griffiths <alan.griffiths@canonical.com> Thu, 02 Feb 2017 17:26:54 +0000
29+
30 miral (1.2.0) UNRELEASED; urgency=medium
31
32- *
33+ * New upstream release 1.2.0 (https://launchpad.net/miral/+milestone/1.2)
34+
35+ - ABI summary:
36+ . miral ABI unchanged at 2
37+ - Enhancements:
38+ . New libmirclientcpp-dev package "C++ wrapper for libmirclient". (Split
39+ from libmiral-dev)
40+ . Give miral-app and miral-desktop a good default for -bindir
41+ . More surface to window renaming to reflect Mir name changes
42+ . Refresh the "Building and Using MirAL" doc
43+ - Bugs fixed:
44+ . Chrome-less shell hint does not work any more (LP: #1658117)
45+ . WindowSpec::set_state() wrapper for mir_window_spec_set_state()
46+ (LP: #1661256)
47+ . "$ miral-app -kiosk" fails with "Unknown command line options:
48+ --desktop_file_hint=miral-shell.desktop" (LP: #1660933)
49+ . libmiral] Fix focus and movement rules for Input Method and Satellite
50+ windows. (LP: #1660691)
51+
52
53 -- Alan Griffiths <alan.griffiths@canonical.com> Thu, 02 Feb 2017 17:26:54 +0000
54
55@@ -15,13 +45,10 @@
56 enums.
57 . Logging of exceptions added to --window-management-trace
58 . Rename WindowManagementPolicy::place_new_surface => place_new_window"
59- . New libmirclientcpp-dev package "C++ wrapper for libmirclient". (Split
60- from libmiral-dev)
61 - Bugs fixed:
62 . top-level window is not raised along with its child (LP: #1658085)
63 . miral-shell depends on default cursor theme being installed
64 (LP: #1658159)
65- . Chrome-less shell hint does not work any more (LP: #1658117)
66
67 -- Cemil Azizoglu <cemil.azizoglu@canonical.com> Fri, 27 Jan 2017 03:02:28 +0000
68
69
70=== modified file 'debian/libmiral2.symbols'
71--- debian/libmiral2.symbols 2017-02-14 11:49:59 +0000
72+++ debian/libmiral2.symbols 2017-02-15 17:24:17 +0000
73@@ -1,7 +1,5 @@
74 libmiral.so.2 libmiral2 #MINVER#
75 MIRAL_1.0@MIRAL_1.0 1.0.0
76- (c++)"miral::WindowInfo::shell_chrome(MirShellChrome)@MIRAL_1.2" 1.2.0
77- (c++)"miral::WindowInfo::shell_chrome() const@MIRAL_1.2" 1.2.0
78 (c++)"miral::WindowInfo::height_inc(mir::geometry::detail::IntWrapper<mir::geometry::DeltaYTag>)@MIRAL_1.0" 1.0.0
79 (c++)"miral::WindowInfo::max_aspect(miral::WindowSpecification::AspectRatio)@MIRAL_1.0" 1.0.0
80 (c++)"miral::WindowInfo::max_height(mir::geometry::detail::IntWrapper<mir::geometry::HeightTag>)@MIRAL_1.0" 1.0.0
81@@ -365,8 +363,21 @@
82 (c++)"miral::WindowInfo::can_morph_to(MirWindowType) const@MIRAL_1.1" 1.1.0
83 (c++)"miral::CanonicalWindowManagerPolicy::place_new_window(miral::ApplicationInfo const&, miral::WindowSpecification const&)@MIRAL_1.1" 1.1.0
84 MIRAL_1.2@MIRAL_1.2 1.2.0
85+ (c++)"miral::InternalClientLauncher::launch(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::function<void (mir::client::Connection)> const&, std::function<void (std::weak_ptr<mir::scene::Session>)> const&) const@MIRAL_1.2" 1.2.0
86 (c++)"miral::StartupInternalClient::StartupInternalClient(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::function<void (mir::client::Connection)>, std::function<void (std::weak_ptr<mir::scene::Session>)>)@MIRAL_1.2" 1.2.0
87- (c++)"miral::InternalClientLauncher::launch(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::function<void (mir::client::Connection)> const&, std::function<void (std::weak_ptr<mir::scene::Session>)> const&) const@MIRAL_1.2" 1.2.0
88+ (c++)"miral::WindowInfo::shell_chrome(MirShellChrome)@MIRAL_1.2" 1.2.0
89+ (c++)"miral::WindowInfo::shell_chrome() const@MIRAL_1.2" 1.2.0
90 (c++)"miral::WindowManagerTools::drag_window(miral::Window const&, mir::geometry::Displacement)@MIRAL_1.2" 1.2.0
91+ (c++)"miral::WindowManagerTools::create_workspace()@MIRAL_1.2" 1.2.0
92+ (c++)"miral::WindowManagerTools::add_tree_to_workspace(miral::Window const&, std::shared_ptr<miral::Workspace> const&)@MIRAL_1.2" 1.2.0
93+ (c++)"miral::WindowManagerTools::remove_tree_from_workspace(miral::Window const&, std::shared_ptr<miral::Workspace> const&)@MIRAL_1.2" 1.2.0
94+ (c++)"miral::operator<(miral::Window const&, miral::Window const&)@MIRAL_1.0" 1.2.0
95 (c++)"typeinfo for miral::ApplicationAuthorizer@MIRAL_1.0" 1.2.0
96 (c++)"typeinfo for miral::ApplicationAuthorizer1@MIRAL_1.2" 1.2.0
97+ MIRAL_1.3@MIRAL_1.3 1.3.0
98+ (c++)"miral::WorkspacePolicy::advise_adding_to_workspace(std::shared_ptr<miral::Workspace> const&, std::vector<miral::Window, std::allocator<miral::Window> > const&)@MIRAL_1.3" 1.2.0
99+ (c++)"miral::WorkspacePolicy::advise_removing_from_workspace(std::shared_ptr<miral::Workspace> const&, std::vector<miral::Window, std::allocator<miral::Window> > const&)@MIRAL_1.3" 1.2.0
100+ (c++)"miral::WindowManagerTools::for_each_window_in_workspace(std::shared_ptr<miral::Workspace> const&, std::function<void (miral::Window const&)> const&)@MIRAL_1.3" 1.2.0
101+ (c++)"miral::WindowManagerTools::for_each_workspace_containing(miral::Window const&, std::function<void (std::shared_ptr<miral::Workspace> const&)> const&)@MIRAL_1.3" 1.2.0
102+ (c++)"typeinfo for miral::WorkspacePolicy@MIRAL_1.3" 1.2.0
103+ (c++)"vtable for miral::WorkspacePolicy@MIRAL_1.3" 1.2.0
104\ No newline at end of file
105
106=== modified file 'include/mir/client/window_id.h'
107--- include/mir/client/window_id.h 2017-02-14 13:50:07 +0000
108+++ include/mir/client/window_id.h 2017-02-15 17:24:17 +0000
109@@ -27,7 +27,7 @@
110 #include <mir_toolkit/mir_persistent_id.h>
111 #endif
112
113-#if MIR_CLIENT_API_VERSION < MIR_VERSION_NUMBER(0, 27, 0)
114+#if MIR_CLIENT_API_VERSION < MIR_VERSION_NUMBER(0, 26, 1)
115 #if MIR_CLIENT_VERSION < MIR_VERSION_NUMBER(3, 5, 0)
116 auto const mir_window_request_window_id_sync = mir_surface_request_persistent_id_sync;
117 #else
118
119=== modified file 'include/miral/window.h'
120--- include/miral/window.h 2016-08-05 08:59:05 +0000
121+++ include/miral/window.h 2017-02-15 17:24:17 +0000
122@@ -63,15 +63,20 @@
123 friend bool operator==(Window const& lhs, Window const& rhs);
124 friend bool operator==(std::shared_ptr<mir::scene::Surface> const& lhs, Window const& rhs);
125 friend bool operator==(Window const& lhs, std::shared_ptr<mir::scene::Surface> const& rhs);
126+ friend bool operator<(Window const& lhs, Window const& rhs);
127 };
128
129 bool operator==(Window const& lhs, Window const& rhs);
130 bool operator==(std::shared_ptr<mir::scene::Surface> const& lhs, Window const& rhs);
131 bool operator==(Window const& lhs, std::shared_ptr<mir::scene::Surface> const& rhs);
132+bool operator<(Window const& lhs, Window const& rhs);
133
134 inline bool operator!=(Window const& lhs, Window const& rhs) { return !(lhs == rhs); }
135 inline bool operator!=(std::shared_ptr<mir::scene::Surface> const& lhs, Window const& rhs) { return !(lhs == rhs); }
136 inline bool operator!=(Window const& lhs, std::shared_ptr<mir::scene::Surface> const& rhs) { return !(lhs == rhs); }
137+inline bool operator>(Window const& lhs, Window const& rhs) { return rhs < lhs; }
138+inline bool operator<=(Window const& lhs, Window const& rhs) { return !(lhs > rhs); }
139+inline bool operator>=(Window const& lhs, Window const& rhs) { return !(lhs < rhs); }
140 }
141
142 #endif //MIRAL_WINDOW_H
143
144=== modified file 'include/miral/window_manager_tools.h'
145--- include/miral/window_manager_tools.h 2017-02-10 10:44:12 +0000
146+++ include/miral/window_manager_tools.h 2017-02-15 17:24:17 +0000
147@@ -1,5 +1,5 @@
148 /*
149- * Copyright © 2016 Canonical Ltd.
150+ * Copyright © 2016-2017 Canonical Ltd.
151 *
152 * This program is free software: you can redistribute it and/or modify it
153 * under the terms of the GNU General Public License version 3,
154@@ -39,6 +39,19 @@
155 struct ApplicationInfo;
156 class WindowSpecification;
157
158+/**
159+ * Workspace is intentionally opaque in the miral API. Its only purpose is to
160+ * provide a shared_ptr which is used as an identifier.
161+ *
162+ * The MirAL implementation of workspaces only prescribes the following:
163+ * o When child windows are created they are added to all(any) workspaces of parent
164+ * o Focus changes will first try windows with a common workspace
165+ * o Adding/removing windows to a workspace affects the whole ancestor/decendent tree
166+ *
167+ * The presentation of workspaces is left entirely to the policy
168+ */
169+class Workspace;
170+
171 class WindowManagerToolsImplementation;
172
173 /// Window management functions for querying and updating MirAL's model
174@@ -158,6 +171,47 @@
175
176 /// Set a default size and position to reflect state change
177 void place_and_size_for_state(WindowSpecification& modifications, WindowInfo const& window_info) const;
178+
179+ /** Create a workspace.
180+ * \remark the tools hold only a weak_ptr<> to the workspace - there is no need for an explicit "destroy".
181+ * @return a shared_ptr owning the workspace
182+ */
183+ auto create_workspace() -> std::shared_ptr<Workspace>;
184+
185+ /**
186+ * Add the tree containing window to a workspace
187+ * @param window the window
188+ * @param workspace the workspace;
189+ */
190+ void add_tree_to_workspace(Window const& window, std::shared_ptr<Workspace> const& workspace);
191+
192+ /**
193+ * Remove the tree containing window from a workspace
194+ * @param window the window
195+ * @param workspace the workspace;
196+ */
197+ void remove_tree_from_workspace(Window const& window, std::shared_ptr<Workspace> const& workspace);
198+
199+ /**
200+ * invoke callback with each workspace containing window
201+ * \warning it is unsafe to add or remove windows from workspaces from the callback during enumeration
202+ * @param window
203+ * @param callback
204+ */
205+ void for_each_workspace_containing(
206+ Window const& window,
207+ std::function<void(std::shared_ptr<Workspace> const& workspace)> const& callback);
208+
209+ /**
210+ * invoke callback with each window contained in workspace
211+ * \warning it is unsafe to add or remove windows from workspaces from the callback during enumeration
212+ * @param workspace
213+ * @param callback
214+ */
215+ void for_each_window_in_workspace(
216+ std::shared_ptr<Workspace> const& workspace,
217+ std::function<void(Window const& window)> const& callback);
218+
219 /** @} */
220
221 /** Multi-thread support
222
223=== added file 'include/miral/workspace_policy.h'
224--- include/miral/workspace_policy.h 1970-01-01 00:00:00 +0000
225+++ include/miral/workspace_policy.h 2017-02-15 17:24:17 +0000
226@@ -0,0 +1,88 @@
227+/*
228+ * Copyright © 2017 Canonical Ltd.
229+ *
230+ * This program is free software: you can redistribute it and/or modify it
231+ * under the terms of the GNU General Public License version 3,
232+ * as published by the Free Software Foundation.
233+ *
234+ * This program is distributed in the hope that it will be useful,
235+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
236+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
237+ * GNU General Public License for more details.
238+ *
239+ * You should have received a copy of the GNU General Public License
240+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
241+ *
242+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
243+ */
244+
245+#ifndef MIRAL_WORKSPACE_POLICY_H
246+#define MIRAL_WORKSPACE_POLICY_H
247+
248+#include "miral/version.h"
249+
250+#include <memory>
251+#include <vector>
252+
253+namespace miral
254+{
255+class Window;
256+
257+/**
258+ * Workspace is intentionally opaque in the miral API. Its only purpose is to
259+ * provide a shared_ptr which is used as an identifier.
260+ */
261+class Workspace;
262+
263+/**
264+ * Advise changes to workspaces.
265+ *
266+ * \note This interface is intended to be implemented by a WindowManagementPolicy
267+ * implementation, we can't add these functions directly to that interface without
268+ * breaking ABI (the vtab could be incompatible).
269+ * When initializing the window manager this interface will be detected by
270+ * dynamic_cast and registered accordingly.
271+ */
272+class WorkspacePolicy
273+{
274+public:
275+/** @name notification of WM events that the policy may need to track.
276+ * These calls happen "under lock" and are wrapped by the usual
277+ * WindowManagementPolicy::advise_begin(), advise_end() calls.
278+ * They should not call WindowManagerTools::invoke_under_lock()
279+ * @{ */
280+
281+ /** Notification that windows are being added to a workspace.
282+ * These windows are ordered with parents before children,
283+ * and form a single tree rooted at the first element.
284+ *
285+ * @param workspace the workspace
286+ * @param windows the windows
287+ */
288+ virtual void advise_adding_to_workspace(
289+ std::shared_ptr<Workspace> const& workspace,
290+ std::vector<Window> const& windows);
291+
292+ /** Notification that windows are being removed from a workspace.
293+ * These windows are ordered with parents before children,
294+ * and form a single tree rooted at the first element.
295+ *
296+ * @param workspace the workspace
297+ * @param windows the windows
298+ */
299+ virtual void advise_removing_from_workspace(
300+ std::shared_ptr<Workspace> const& workspace,
301+ std::vector<Window> const& windows);
302+
303+/** @} */
304+
305+ virtual ~WorkspacePolicy() = default;
306+ WorkspacePolicy() = default;
307+ WorkspacePolicy(WorkspacePolicy const&) = delete;
308+ WorkspacePolicy& operator=(WorkspacePolicy const&) = delete;
309+};
310+#if MIRAL_VERSION >= MIR_VERSION_NUMBER(2, 0, 0)
311+#error "We've presumably broken ABI - please roll this interface into WindowManagementPolicy"
312+#endif
313+}
314+#endif //MIRAL_WORKSPACE_POLICY_H
315
316=== modified file 'miral/CMakeLists.txt'
317--- miral/CMakeLists.txt 2017-02-14 15:46:32 +0000
318+++ miral/CMakeLists.txt 2017-02-15 17:24:17 +0000
319@@ -49,6 +49,7 @@
320 set_command_line_hander.cpp ${CMAKE_SOURCE_DIR}/include/miral/set_command_line_hander.h
321 set_terminator.cpp ${CMAKE_SOURCE_DIR}/include/miral/set_terminator.h
322 set_window_managment_policy.cpp ${CMAKE_SOURCE_DIR}/include/miral/set_window_managment_policy.h
323+ workspace_policy.cpp ${CMAKE_SOURCE_DIR}/include/miral/workspace_policy.h
324 window_management_policy.cpp ${CMAKE_SOURCE_DIR}/include/miral/window_management_policy.h
325 window_manager_tools.cpp ${CMAKE_SOURCE_DIR}/include/miral/window_manager_tools.h
326 ${CMAKE_SOURCE_DIR}/include/mir/client/window_spec.h
327
328=== modified file 'miral/basic_window_manager.cpp'
329--- miral/basic_window_manager.cpp 2017-02-08 17:17:41 +0000
330+++ miral/basic_window_manager.cpp 2017-02-15 17:24:17 +0000
331@@ -1,5 +1,5 @@
332 /*
333- * Copyright © 2015-2016 Canonical Ltd.
334+ * Copyright © 2015-2017 Canonical Ltd.
335 *
336 * This program is free software: you can redistribute it and/or modify it
337 * under the terms of the GNU General Public License version 3,
338@@ -18,6 +18,7 @@
339
340 #include "basic_window_manager.h"
341 #include "miral/window_manager_tools.h"
342+#include "miral/workspace_policy.h"
343
344 #include <mir/scene/session.h>
345 #include <mir/scene/surface.h>
346@@ -37,15 +38,11 @@
347 namespace
348 {
349 int const title_bar_height = 12;
350+}
351
352-struct Locker
353+struct miral::BasicWindowManager::Locker
354 {
355- Locker(std::mutex& mutex, std::unique_ptr<miral::WindowManagementPolicy> const& policy) :
356- lock{mutex},
357- policy{policy.get()}
358- {
359- policy->advise_begin();
360- }
361+ explicit Locker(miral::BasicWindowManager* self);
362
363 ~Locker()
364 {
365@@ -53,9 +50,53 @@
366 }
367
368 std::lock_guard<std::mutex> const lock;
369- miral::WindowManagementPolicy* const policy;
370+ WindowManagementPolicy* const policy;
371 };
372-}
373+
374+miral::BasicWindowManager::Locker::Locker(BasicWindowManager* self) :
375+ lock{self->mutex},
376+ policy{self->policy.get()}
377+{
378+ policy->advise_begin();
379+ std::vector<std::weak_ptr<Workspace>> workspaces;
380+ {
381+ std::lock_guard<std::mutex> const lock{self->dead_workspaces.dead_workspaces_mutex};
382+ workspaces.swap(self->dead_workspaces.workspaces);
383+ }
384+
385+ for (auto const& workspace : workspaces)
386+ self->workspaces_to_windows.left.erase(workspace);
387+}
388+
389+namespace
390+{
391+
392+auto find_workspace_policy(std::unique_ptr<miral::WindowManagementPolicy> const& policy) -> miral::WorkspacePolicy*
393+{
394+ miral::WorkspacePolicy* result = dynamic_cast<miral::WorkspacePolicy*>(policy.get());
395+
396+ if (result)
397+ return result;
398+
399+ struct NullWorkspacePolicy : miral::WorkspacePolicy
400+ {
401+ void advise_adding_to_workspace(
402+ std::shared_ptr<miral::Workspace> const&, std::vector<miral::Window> const&) override
403+ {
404+ }
405+
406+ void advise_removing_from_workspace(
407+ std::shared_ptr<miral::Workspace> const&, std::vector<miral::Window> const&) override
408+ {
409+ }
410+ };
411+
412+ static NullWorkspacePolicy null_workspace_policy;
413+
414+ return &null_workspace_policy;
415+}
416+}
417+
418
419 miral::BasicWindowManager::BasicWindowManager(
420 shell::FocusController* focus_controller,
421@@ -65,19 +106,20 @@
422 focus_controller(focus_controller),
423 display_layout(display_layout),
424 persistent_surface_store{persistent_surface_store},
425- policy(build(WindowManagerTools{this}))
426+ policy(build(WindowManagerTools{this})),
427+ workspace_policy{find_workspace_policy(policy)}
428 {
429 }
430
431 void miral::BasicWindowManager::add_session(std::shared_ptr<scene::Session> const& session)
432 {
433- Locker lock{mutex, policy};
434+ Locker lock{this};
435 policy->advise_new_app(app_info[session] = ApplicationInfo(session));
436 }
437
438 void miral::BasicWindowManager::remove_session(std::shared_ptr<scene::Session> const& session)
439 {
440- Locker lock{mutex, policy};
441+ Locker lock{this};
442 policy->advise_delete_app(app_info[session]);
443 app_info.erase(session);
444 }
445@@ -88,7 +130,7 @@
446 std::function<frontend::SurfaceId(std::shared_ptr<scene::Session> const& session, scene::SurfaceCreationParameters const& params)> const& build)
447 -> frontend::SurfaceId
448 {
449- Locker lock{mutex, policy};
450+ Locker lock{this};
451
452 auto& session_info = info_for(session);
453
454@@ -112,6 +154,9 @@
455 if (parent)
456 info_for(parent).add_child(window);
457
458+ for_each_workspace_containing(parent,
459+ [&](std::shared_ptr<miral::Workspace> const& workspace) { add_tree_to_workspace(window, workspace); });
460+
461 if (window_info.state() == mir_window_state_fullscreen)
462 fullscreen_surfaces.insert(window_info.window());
463
464@@ -120,7 +165,7 @@
465 std::shared_ptr<scene::Surface> const scene_surface = window_info.window();
466 scene_surface->add_observer(std::make_shared<shell::SurfaceReadyObserver>(
467 [this, &window_info](std::shared_ptr<scene::Session> const&, std::shared_ptr<scene::Surface> const&)
468- { Locker lock{mutex, policy}; policy->handle_window_ready(window_info); },
469+ { Locker lock{this}; policy->handle_window_ready(window_info); },
470 session,
471 scene_surface));
472
473@@ -141,7 +186,7 @@
474 std::shared_ptr<scene::Surface> const& surface,
475 shell::SurfaceSpecification const& modifications)
476 {
477- Locker lock{mutex, policy};
478+ Locker lock{this};
479 auto& info = info_for(surface);
480 WindowSpecification mods{modifications};
481 validate_modification_request(mods, info);
482@@ -153,16 +198,28 @@
483 std::shared_ptr<scene::Session> const& session,
484 std::weak_ptr<scene::Surface> const& surface)
485 {
486- Locker lock{mutex, policy};
487+ Locker lock{this};
488 remove_window(session, info_for(surface));
489 }
490
491 void miral::BasicWindowManager::remove_window(Application const& application, miral::WindowInfo const& info)
492 {
493+ bool const is_active_window{mru_active_windows.top() == info.window()};
494+ auto const workspaces_containing_window = workspaces_containing(info.window());
495+
496+ {
497+ std::vector<Window> const windows_removed{info.window()};
498+
499+ for (auto const& workspace : workspaces_containing_window)
500+ {
501+ workspace_policy->advise_removing_from_workspace(workspace, windows_removed);
502+ }
503+
504+ workspaces_to_windows.right.erase(info.window());
505+ }
506+
507 policy->advise_delete_window(info);
508
509- bool const is_active_window{mru_active_windows.top() == info.window()};
510-
511 info_for(application).remove_window(info.window());
512 mru_active_windows.erase(info.window());
513 fullscreen_surfaces.erase(info.window());
514@@ -175,30 +232,66 @@
515
516 if (is_active_window)
517 {
518- // Try to make the parent active
519- if (parent && select_active_window(parent))
520- return;
521-
522- if (can_activate_window_for_session(application))
523- return;
524-
525- // Try to activate to recently active window of any application
526- {
527- miral::Window new_focus;
528-
529- mru_active_windows.enumerate([&](miral::Window& window)
530+ refocus(application, parent, workspaces_containing_window);
531+ }
532+}
533+
534+void miral::BasicWindowManager::refocus(
535+ miral::Application const& application, miral::Window const& parent,
536+ std::vector<std::shared_ptr<Workspace>> const& workspaces_containing_window)
537+{
538+ // Try to make the parent active
539+ if (parent && select_active_window(parent))
540+ return;
541+
542+ if (can_activate_window_for_session_in_workspace(application, workspaces_containing_window))
543+ return;
544+
545+ // Try to activate to recently active window of any application in a shared workspace
546+ {
547+ miral::Window new_focus;
548+
549+ mru_active_windows.enumerate([&](miral::Window& window)
550+ {
551+ // select_active_window() calls set_focus_to() which updates mru_active_windows and changes window
552+ auto const w = window;
553+
554+ for (auto const& workspace : workspaces_containing(w))
555 {
556- // select_active_window() calls set_focus_to() which updates mru_active_windows and changes window
557- auto const w = window;
558- return !(new_focus = select_active_window(w));
559- });
560-
561- if (new_focus) return;
562- }
563-
564- // Fallback to cycling through applications
565- focus_next_application();
566- }
567+ for (auto const& ww : workspaces_containing_window)
568+ {
569+ if (ww == workspace)
570+ {
571+ return !(new_focus = select_active_window(w));
572+ }
573+ }
574+ }
575+
576+ return true;
577+ });
578+
579+ if (new_focus) return;
580+ }
581+
582+ if (can_activate_window_for_session(application))
583+ return;
584+
585+ // Try to activate to recently active window of any application
586+ {
587+ miral::Window new_focus;
588+
589+ mru_active_windows.enumerate([&](miral::Window& window)
590+ {
591+ // select_active_window() calls set_focus_to() which updates mru_active_windows and changes window
592+ auto const w = window;
593+ return !(new_focus = select_active_window(w));
594+ });
595+
596+ if (new_focus) return;
597+ }
598+
599+ // Fallback to cycling through applications
600+ focus_next_application();
601 }
602
603 void miral::BasicWindowManager::erase(miral::WindowInfo const& info)
604@@ -214,7 +307,7 @@
605
606 void miral::BasicWindowManager::add_display(geometry::Rectangle const& area)
607 {
608- Locker lock{mutex, policy};
609+ Locker lock{this};
610 displays.add(area);
611
612 for (auto window : fullscreen_surfaces)
613@@ -230,7 +323,7 @@
614
615 void miral::BasicWindowManager::remove_display(geometry::Rectangle const& area)
616 {
617- Locker lock{mutex, policy};
618+ Locker lock{this};
619 displays.remove(area);
620 for (auto window : fullscreen_surfaces)
621 {
622@@ -245,21 +338,21 @@
623
624 bool miral::BasicWindowManager::handle_keyboard_event(MirKeyboardEvent const* event)
625 {
626- Locker lock{mutex, policy};
627+ Locker lock{this};
628 update_event_timestamp(event);
629 return policy->handle_keyboard_event(event);
630 }
631
632 bool miral::BasicWindowManager::handle_touch_event(MirTouchEvent const* event)
633 {
634- Locker lock{mutex, policy};
635+ Locker lock{this};
636 update_event_timestamp(event);
637 return policy->handle_touch_event(event);
638 }
639
640 bool miral::BasicWindowManager::handle_pointer_event(MirPointerEvent const* event)
641 {
642- Locker lock{mutex, policy};
643+ Locker lock{this};
644 update_event_timestamp(event);
645
646 cursor = {
647@@ -274,7 +367,7 @@
648 std::shared_ptr<scene::Surface> const& surface,
649 uint64_t timestamp)
650 {
651- Locker lock{mutex, policy};
652+ Locker lock{this};
653 if (timestamp >= last_input_event_timestamp)
654 policy->handle_raise_window(info_for(surface));
655 }
656@@ -315,7 +408,7 @@
657 return surface->configure(attrib, value);
658 }
659
660- Locker lock{mutex, policy};
661+ Locker lock{this};
662 auto& info = info_for(surface);
663
664 validate_modification_request(modification, info);
665@@ -407,6 +500,28 @@
666
667 void miral::BasicWindowManager::focus_next_application()
668 {
669+ if (auto const prev = active_window())
670+ {
671+ auto const workspaces_containing_window = workspaces_containing(prev);
672+
673+ if (!workspaces_containing_window.empty())
674+ {
675+ do
676+ {
677+ focus_controller->focus_next_session();
678+
679+ if (can_activate_window_for_session_in_workspace(
680+ focus_controller->focused_session(),
681+ workspaces_containing_window))
682+ {
683+ return;
684+ }
685+ }
686+ while (focus_controller->focused_session() != prev.application());
687+ }
688+
689+ }
690+
691 focus_controller->focus_next_session();
692
693 if (can_activate_window_for_session(focus_controller->focused_session()))
694@@ -417,15 +532,67 @@
695 select_active_window(focussed_surface ? info_for(focussed_surface).window() : Window{});
696 }
697
698+auto miral::BasicWindowManager::workspaces_containing(Window const& window) const
699+-> std::vector<std::shared_ptr<Workspace>>
700+{
701+ auto const iter_pair = workspaces_to_windows.right.equal_range(window);
702+
703+ std::vector<std::shared_ptr<Workspace>> workspaces_containing_window;
704+ for (auto kv = iter_pair.first; kv != iter_pair.second; ++kv)
705+ {
706+ if (auto const workspace = kv->second.lock())
707+ {
708+ workspaces_containing_window.push_back(workspace);
709+ }
710+ }
711+
712+ return workspaces_containing_window;
713+}
714+
715 void miral::BasicWindowManager::focus_next_within_application()
716 {
717 if (auto const prev = active_window())
718 {
719+ auto const workspaces_containing_window = workspaces_containing(prev);
720 auto const& siblings = info_for(prev.application()).windows();
721 auto current = find(begin(siblings), end(siblings), prev);
722
723 if (current != end(siblings))
724 {
725+ while (++current != end(siblings))
726+ {
727+ for (auto const& workspace : workspaces_containing(*current))
728+ {
729+ for (auto const& ww : workspaces_containing_window)
730+ {
731+ if (ww == workspace)
732+ {
733+ if (prev != select_active_window(*current))
734+ return;
735+ }
736+ }
737+ }
738+ }
739+ }
740+
741+ for (current = begin(siblings); *current != prev; ++current)
742+ {
743+ for (auto const& workspace : workspaces_containing(*current))
744+ {
745+ for (auto const& ww : workspaces_containing_window)
746+ {
747+ if (ww == workspace)
748+ {
749+ if (prev != select_active_window(*current))
750+ return;
751+ }
752+ }
753+ }
754+ }
755+
756+ current = find(begin(siblings), end(siblings), prev);
757+ if (current != end(siblings))
758+ {
759 while (++current != end(siblings) && prev == select_active_window(*current))
760 ;
761 }
762@@ -886,12 +1053,35 @@
763
764 if (window == active_window())
765 {
766+ auto const workspaces_containing_window = workspaces_containing(window);
767+
768 // Try to activate to recently active window of any application
769 mru_active_windows.enumerate([&](Window& candidate)
770 {
771 if (candidate == window)
772 return true;
773 auto const w = candidate;
774+ for (auto const& workspace : workspaces_containing(w))
775+ {
776+ for (auto const& ww : workspaces_containing_window)
777+ {
778+ if (ww == workspace)
779+ {
780+ return !(select_active_window(w));
781+ }
782+ }
783+ }
784+
785+ return true;
786+ });
787+
788+ // Try to activate to recently active window of any application
789+ if (window == active_window() || !active_window())
790+ mru_active_windows.enumerate([&](Window& candidate)
791+ {
792+ if (candidate == window)
793+ return true;
794+ auto const w = candidate;
795 return !(select_active_window(w));
796 });
797
798@@ -945,7 +1135,7 @@
799
800 void miral::BasicWindowManager::invoke_under_lock(std::function<void()> const& callback)
801 {
802- Locker lock{mutex, policy};
803+ Locker lock{this};
804 callback();
805 }
806
807@@ -1076,6 +1266,35 @@
808 return new_focus;
809 }
810
811+auto miral::BasicWindowManager::can_activate_window_for_session_in_workspace(
812+ Application const& session,
813+ std::vector<std::shared_ptr<Workspace>> const& workspaces) -> bool
814+{
815+ miral::Window new_focus;
816+
817+ mru_active_windows.enumerate([&](miral::Window& window)
818+ {
819+ // select_active_window() calls set_focus_to() which updates mru_active_windows and changes window
820+ auto const w = window;
821+
822+ if (w.application() != session)
823+ return true;
824+
825+ for (auto const& workspace : workspaces_containing(w))
826+ {
827+ for (auto const& ww : workspaces)
828+ {
829+ if (ww == workspace)
830+ return !(new_focus = select_active_window(w));
831+ }
832+ }
833+
834+ return true;
835+ });
836+
837+ return new_focus;
838+}
839+
840 auto miral::BasicWindowManager::place_new_surface(ApplicationInfo const& app_info, WindowSpecification parameters)
841 -> WindowSpecification
842 {
843@@ -1610,3 +1829,139 @@
844 BOOST_THROW_EXCEPTION(std::runtime_error("height must be positive"));
845 }
846 }
847+
848+class miral::Workspace
849+{
850+public:
851+ explicit Workspace(miral::BasicWindowManager::DeadWorkspaces& dead_workspaces) :
852+ dead_workspaces{dead_workspaces} {}
853+
854+ std::weak_ptr<Workspace> self;
855+
856+ ~Workspace()
857+ {
858+ std::lock_guard<std::mutex> lock {dead_workspaces.dead_workspaces_mutex};
859+ dead_workspaces.workspaces.push_back(self);
860+ }
861+
862+private:
863+ miral::BasicWindowManager::DeadWorkspaces& dead_workspaces;
864+};
865+
866+auto miral::BasicWindowManager::create_workspace() -> std::shared_ptr<Workspace>
867+{
868+ auto const result = std::make_shared<Workspace>(dead_workspaces);
869+ result->self = result;
870+ return result;
871+}
872+
873+void miral::BasicWindowManager::add_tree_to_workspace(
874+ miral::Window const& window, std::shared_ptr<miral::Workspace> const& workspace)
875+{
876+ if (!window) return;
877+
878+ auto root = window;
879+ auto const* info = &info_for(root);
880+
881+ while (auto const& parent = info->parent())
882+ {
883+ root = parent;
884+ info = &info_for(root);
885+ }
886+
887+ std::vector<Window> windows;
888+
889+ std::function<void(WindowInfo const& info)> const add_children =
890+ [&,this](WindowInfo const& info)
891+ {
892+ for (auto const& child : info.children())
893+ {
894+ windows.push_back(child);
895+ add_children(info_for(child));
896+ }
897+ };
898+
899+ windows.push_back(root);
900+ add_children(*info);
901+
902+ auto const iter_pair = workspaces_to_windows.left.equal_range(workspace);
903+
904+ std::vector<Window> windows_added;
905+
906+ for (auto& w : windows)
907+ {
908+ if (!std::count_if(iter_pair.first, iter_pair.second,
909+ [&w](wwbimap_t::left_value_type const& kv) { return kv.second == w; }))
910+ {
911+ workspaces_to_windows.left.insert(wwbimap_t::left_value_type{workspace, w});
912+ windows_added.push_back(w);
913+ }
914+ }
915+
916+ if (!windows_added.empty())
917+ workspace_policy->advise_adding_to_workspace(workspace, windows_added);
918+}
919+
920+void miral::BasicWindowManager::remove_tree_from_workspace(
921+ miral::Window const& window, std::shared_ptr<miral::Workspace> const& workspace)
922+{
923+ if (!window) return;
924+
925+ auto root = window;
926+ auto const* info = &info_for(root);
927+
928+ while (auto const& parent = info->parent())
929+ {
930+ root = parent;
931+ info = &info_for(root);
932+ }
933+
934+ std::vector<Window> windows;
935+
936+ std::function<void(WindowInfo const& info)> const add_children =
937+ [&,this](WindowInfo const& info)
938+ {
939+ for (auto const& child : info.children())
940+ {
941+ windows.push_back(child);
942+ add_children(info_for(child));
943+ }
944+ };
945+
946+ windows.push_back(root);
947+ add_children(*info);
948+
949+ std::vector<Window> windows_removed;
950+
951+ auto const iter_pair = workspaces_to_windows.left.equal_range(workspace);
952+ for (auto kv = iter_pair.first; kv != iter_pair.second; ++kv)
953+ {
954+ if (std::count(begin(windows), end(windows), kv->second))
955+ {
956+ workspaces_to_windows.left.erase(kv);
957+ windows_removed.push_back(kv->second);
958+ }
959+ }
960+
961+ if (!windows_removed.empty())
962+ workspace_policy->advise_removing_from_workspace(workspace, windows_removed);
963+}
964+
965+void miral::BasicWindowManager::for_each_workspace_containing(
966+ miral::Window const& window, std::function<void(std::shared_ptr<miral::Workspace> const&)> const& callback)
967+{
968+ auto const iter_pair = workspaces_to_windows.right.equal_range(window);
969+ for (auto kv = iter_pair.first; kv != iter_pair.second; ++kv)
970+ {
971+ if (auto const workspace = kv->second.lock())
972+ callback(workspace);
973+ }
974+}
975+
976+void miral::BasicWindowManager::for_each_window_in_workspace(
977+ std::shared_ptr<miral::Workspace> const& workspace, std::function<void(miral::Window const&)> const& callback)
978+{
979+ auto const iter_pair = workspaces_to_windows.left.equal_range(workspace);
980+ for (auto kv = iter_pair.first; kv != iter_pair.second; ++kv)
981+ callback(kv->second);
982+}
983
984=== modified file 'miral/basic_window_manager.h'
985--- miral/basic_window_manager.h 2017-02-02 17:18:42 +0000
986+++ miral/basic_window_manager.h 2017-02-15 17:24:17 +0000
987@@ -1,5 +1,5 @@
988 /*
989- * Copyright © 2015-2016 Canonical Ltd.
990+ * Copyright © 2015-2017 Canonical Ltd.
991 *
992 * This program is free software: you can redistribute it and/or modify it
993 * under the terms of the GNU General Public License version 3,
994@@ -31,6 +31,9 @@
995 #include <mir/shell/abstract_shell.h>
996 #include <mir/shell/window_manager.h>
997
998+#include <boost/bimap.hpp>
999+#include <boost/bimap/multiset_of.hpp>
1000+
1001 #include <map>
1002 #include <mutex>
1003
1004@@ -41,6 +44,7 @@
1005
1006 namespace miral
1007 {
1008+class WorkspacePolicy;
1009 using mir::shell::SurfaceSet;
1010 using WindowManagementPolicyBuilder =
1011 std::function<std::unique_ptr<miral::WindowManagementPolicy>(miral::WindowManagerTools const& tools)>;
1012@@ -97,6 +101,19 @@
1013 MirWindowAttrib attrib,
1014 int value) override;
1015
1016+ auto create_workspace() -> std::shared_ptr<Workspace> override;
1017+
1018+ void add_tree_to_workspace(Window const& window, std::shared_ptr<Workspace> const& workspace) override;
1019+
1020+ void remove_tree_from_workspace(Window const& window, std::shared_ptr<Workspace> const& workspace) override;
1021+
1022+ void for_each_workspace_containing(
1023+ Window const& window,
1024+ std::function<void(std::shared_ptr<Workspace> const& workspace)> const& callback) override;
1025+
1026+ void for_each_window_in_workspace(
1027+ std::shared_ptr<Workspace> const& workspace, std::function<void(Window const&)> const& callback) override;
1028+
1029 auto count_applications() const -> unsigned int override;
1030
1031 void for_each_application(std::function<void(ApplicationInfo& info)> const& functor) override;
1032@@ -149,6 +166,7 @@
1033 std::shared_ptr<mir::shell::DisplayLayout> const display_layout;
1034 std::shared_ptr<mir::shell::PersistentSurfaceStore> const persistent_surface_store;
1035 std::unique_ptr<WindowManagementPolicy> const policy;
1036+ WorkspacePolicy* const workspace_policy;
1037
1038 std::mutex mutex;
1039 SessionInfoMap app_info;
1040@@ -160,11 +178,30 @@
1041 using FullscreenSurfaces = std::set<Window>;
1042 FullscreenSurfaces fullscreen_surfaces;
1043
1044+ friend class Workspace;
1045+ using wwbimap_t = boost::bimap<
1046+ boost::bimaps::multiset_of<std::weak_ptr<Workspace>, std::owner_less<std::weak_ptr<Workspace>>>,
1047+ boost::bimaps::multiset_of<Window>>;
1048+
1049+ wwbimap_t workspaces_to_windows;
1050+
1051+ // Workspaces may die without any sync with the BWM mutex
1052+ struct DeadWorkspaces
1053+ {
1054+ std::mutex mutable dead_workspaces_mutex;
1055+ std::vector<std::weak_ptr<Workspace>> workspaces;
1056+ } dead_workspaces;
1057+
1058+ struct Locker;
1059+
1060 void update_event_timestamp(MirKeyboardEvent const* kev);
1061 void update_event_timestamp(MirPointerEvent const* pev);
1062 void update_event_timestamp(MirTouchEvent const* tev);
1063
1064 auto can_activate_window_for_session(miral::Application const& session) -> bool;
1065+ auto can_activate_window_for_session_in_workspace(
1066+ miral::Application const& session,
1067+ std::vector<std::shared_ptr<Workspace>> const& workspaces) -> bool;
1068
1069 auto place_new_surface(ApplicationInfo const& app_info, WindowSpecification parameters) -> WindowSpecification;
1070 auto place_relative(mir::geometry::Rectangle const& parent, miral::WindowSpecification const& parameters, Size size)
1071@@ -177,6 +214,9 @@
1072 void set_state(miral::WindowInfo& window_info, MirWindowState value);
1073 auto fullscreen_rect_for(WindowInfo const& window_info) const -> Rectangle;
1074 void remove_window(Application const& application, miral::WindowInfo const& info);
1075+ void refocus(Application const& application, Window const& parent,
1076+ std::vector<std::shared_ptr<Workspace>> const& workspaces_containing_window);
1077+ auto workspaces_containing(Window const& window) const -> std::vector<std::shared_ptr<Workspace>>;
1078 };
1079 }
1080
1081
1082=== modified file 'miral/symbols.map'
1083--- miral/symbols.map 2017-02-15 12:06:25 +0000
1084+++ miral/symbols.map 2017-02-15 17:24:17 +0000
1085@@ -362,8 +362,36 @@
1086 _ZN5miral21StartupInternalClientC?ENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESt8functionIFvN3mir6client10ConnectionEEES7_IFvSt8weak_ptrINS8_5scene7SessionEEEE;
1087 extern "C++" {
1088 miral::WindowInfo::shell_chrome*;
1089+ miral::WindowManagerTools::add_tree_to_workspace*;
1090+ miral::WindowManagerTools::create_workspace*;
1091 miral::WindowManagerTools::drag_window*;
1092+ miral::WindowManagerTools::remove_tree_from_workspace*;
1093 typeinfo?for?miral::ApplicationAuthorizer1;
1094 vtable?for?miral::ApplicationAuthorizer1;
1095 };
1096 } MIRAL_1.1;
1097+
1098+MIRAL_1.3 {
1099+global:
1100+ extern "C++" {
1101+ miral::WindowManagerTools::for_each_window_in_workspace*;
1102+ miral::WindowManagerTools::for_each_workspace_containing*;
1103+ miral::WorkspacePolicy::?WorkspacePolicy*;
1104+ miral::WorkspacePolicy::WorkspacePolicy*;
1105+ miral::WorkspacePolicy::advise_adding_to_workspace*;
1106+ miral::WorkspacePolicy::advise_removing_from_workspace*;
1107+ miral::WorkspacePolicy::operator*;
1108+ miral::toolkit::Window::Window*;
1109+ non-virtual?thunk?to?miral::WorkspacePolicy::?WorkspacePolicy*;
1110+ non-virtual?thunk?to?miral::WorkspacePolicy::advise_adding_to_workspace*;
1111+ non-virtual?thunk?to?miral::WorkspacePolicy::advise_removing_from_workspace*;
1112+ typeinfo?for?miral::WorkspacePolicy;
1113+ typeinfo?for?miral::toolkit::Window;
1114+ typeinfo?for?miral::toolkit::WindowId;
1115+ typeinfo?for?miral::toolkit::WindowSpec;
1116+ vtable?for?miral::WorkspacePolicy;
1117+ vtable?for?miral::toolkit::Window;
1118+ vtable?for?miral::toolkit::WindowId;
1119+ vtable?for?miral::toolkit::WindowSpec;
1120+ };
1121+} MIRAL_1.2;
1122
1123=== modified file 'miral/window.cpp'
1124--- miral/window.cpp 2016-08-05 08:53:45 +0000
1125+++ miral/window.cpp 2017-02-15 17:24:17 +0000
1126@@ -120,3 +120,8 @@
1127 {
1128 return rhs == lhs;
1129 }
1130+
1131+bool miral::operator<(Window const& lhs, Window const& rhs)
1132+{
1133+ return lhs.self.owner_before(rhs.self);
1134+}
1135
1136=== modified file 'miral/window_management_trace.cpp'
1137--- miral/window_management_trace.cpp 2017-02-02 17:18:42 +0000
1138+++ miral/window_management_trace.cpp 2017-02-15 17:24:17 +0000
1139@@ -532,6 +532,45 @@
1140 }
1141 MIRAL_TRACE_EXCEPTION
1142
1143+auto miral::WindowManagementTrace::create_workspace() -> std::shared_ptr<Workspace>
1144+try {
1145+ mir::log_info("%s", __func__);
1146+ return wrapped.create_workspace();
1147+}
1148+MIRAL_TRACE_EXCEPTION
1149+
1150+void miral::WindowManagementTrace::add_tree_to_workspace(
1151+ miral::Window const& window, std::shared_ptr<miral::Workspace> const& workspace)
1152+try {
1153+ mir::log_info("%s window=%s, workspace =%p", __func__, dump_of(window).c_str(), workspace.get());
1154+ wrapped.add_tree_to_workspace(window, workspace);
1155+}
1156+MIRAL_TRACE_EXCEPTION
1157+
1158+void miral::WindowManagementTrace::remove_tree_from_workspace(
1159+ miral::Window const& window, std::shared_ptr<miral::Workspace> const& workspace)
1160+try {
1161+ mir::log_info("%s window=%s, workspace =%p", __func__, dump_of(window).c_str(), workspace.get());
1162+ wrapped.remove_tree_from_workspace(window, workspace);
1163+}
1164+MIRAL_TRACE_EXCEPTION
1165+
1166+void miral::WindowManagementTrace::for_each_workspace_containing(
1167+ miral::Window const& window, std::function<void(std::shared_ptr<miral::Workspace> const&)> const& callback)
1168+try {
1169+ mir::log_info("%s window=%s", __func__, dump_of(window));
1170+ wrapped.for_each_workspace_containing(window, callback);
1171+}
1172+MIRAL_TRACE_EXCEPTION
1173+
1174+void miral::WindowManagementTrace::for_each_window_in_workspace(
1175+ std::shared_ptr<miral::Workspace> const& workspace, std::function<void(miral::Window const&)> const& callback)
1176+try {
1177+ mir::log_info("%s workspace =%p", __func__, workspace.get());
1178+ wrapped.for_each_window_in_workspace(workspace, callback);
1179+}
1180+MIRAL_TRACE_EXCEPTION
1181+
1182 auto miral::WindowManagementTrace::place_new_window(
1183 ApplicationInfo const& app_info,
1184 WindowSpecification const& requested_specification) -> WindowSpecification
1185
1186=== modified file 'miral/window_management_trace.h'
1187--- miral/window_management_trace.h 2017-02-02 17:18:42 +0000
1188+++ miral/window_management_trace.h 2017-02-15 17:24:17 +0000
1189@@ -90,6 +90,19 @@
1190
1191 auto confirm_inherited_move(WindowInfo const& window_info, Displacement movement) -> Rectangle override;
1192
1193+ auto create_workspace() -> std::shared_ptr<Workspace> override;
1194+
1195+ void add_tree_to_workspace(Window const& window, std::shared_ptr<Workspace> const& workspace) override;
1196+
1197+ void remove_tree_from_workspace(Window const& window, std::shared_ptr<Workspace> const& workspace) override;
1198+
1199+ void for_each_workspace_containing(
1200+ Window const& window,
1201+ std::function<void(std::shared_ptr<Workspace> const& workspace)> const& callback) override;
1202+
1203+ void for_each_window_in_workspace(
1204+ std::shared_ptr<Workspace> const& workspace, std::function<void(Window const&)> const& callback) override;
1205+
1206 public:
1207 virtual void advise_begin() override;
1208
1209
1210=== modified file 'miral/window_manager_tools.cpp'
1211--- miral/window_manager_tools.cpp 2017-02-02 17:18:42 +0000
1212+++ miral/window_manager_tools.cpp 2017-02-15 17:24:17 +0000
1213@@ -98,3 +98,26 @@
1214 void miral::WindowManagerTools::place_and_size_for_state(
1215 WindowSpecification& modifications, WindowInfo const& window_info) const
1216 { tools->place_and_size_for_state(modifications, window_info); }
1217+
1218+auto miral::WindowManagerTools::create_workspace() -> std::shared_ptr<miral::Workspace>
1219+{ return tools->create_workspace(); }
1220+
1221+void miral::WindowManagerTools::add_tree_to_workspace(
1222+ miral::Window const& window,
1223+ std::shared_ptr<miral::Workspace> const& workspace)
1224+{ tools->add_tree_to_workspace(window, workspace); }
1225+
1226+void miral::WindowManagerTools::remove_tree_from_workspace(
1227+ miral::Window const& window,
1228+ std::shared_ptr<miral::Workspace> const& workspace)
1229+{ tools->remove_tree_from_workspace(window, workspace); }
1230+
1231+void miral::WindowManagerTools::for_each_workspace_containing(
1232+ miral::Window const& window,
1233+ std::function<void(std::shared_ptr<miral::Workspace> const&)> const& callback)
1234+{ tools->for_each_workspace_containing(window, callback); }
1235+
1236+void miral::WindowManagerTools::for_each_window_in_workspace(
1237+ std::shared_ptr<miral::Workspace> const& workspace,
1238+ std::function<void(miral::Window const&)> const& callback)
1239+{ tools->for_each_window_in_workspace(workspace, callback); }
1240
1241=== modified file 'miral/window_manager_tools_implementation.h'
1242--- miral/window_manager_tools_implementation.h 2017-02-02 17:18:42 +0000
1243+++ miral/window_manager_tools_implementation.h 2017-02-15 17:24:17 +0000
1244@@ -35,6 +35,7 @@
1245 struct WindowInfo;
1246 struct ApplicationInfo;
1247 class WindowSpecification;
1248+class Workspace;
1249
1250 // The interface through which the policy instructs the controller.
1251 class WindowManagerToolsImplementation
1252@@ -69,6 +70,16 @@
1253 virtual auto id_for_window(Window const& window) const -> std::string = 0;
1254 virtual void place_and_size_for_state(WindowSpecification& modifications, WindowInfo const& window_info) const= 0;
1255
1256+ virtual auto create_workspace() -> std::shared_ptr<Workspace> = 0;
1257+ virtual void add_tree_to_workspace(Window const& window, std::shared_ptr<Workspace> const& workspace) = 0;
1258+ virtual void remove_tree_from_workspace(Window const& window, std::shared_ptr<Workspace> const& workspace) = 0;
1259+ virtual void for_each_workspace_containing(
1260+ Window const& window,
1261+ std::function<void(std::shared_ptr<Workspace> const& workspace)> const& callback) = 0;
1262+ virtual void for_each_window_in_workspace(
1263+ std::shared_ptr<Workspace> const& workspace,
1264+ std::function<void(Window const& window)> const& callback) = 0;
1265+
1266 /** @} */
1267
1268 /** @name Multi-thread support
1269
1270=== added file 'miral/workspace_policy.cpp'
1271--- miral/workspace_policy.cpp 1970-01-01 00:00:00 +0000
1272+++ miral/workspace_policy.cpp 2017-02-15 17:24:17 +0000
1273@@ -0,0 +1,29 @@
1274+/*
1275+ * Copyright © 2017 Canonical Ltd.
1276+ *
1277+ * This program is free software: you can redistribute it and/or modify it
1278+ * under the terms of the GNU General Public License version 3,
1279+ * as published by the Free Software Foundation.
1280+ *
1281+ * This program is distributed in the hope that it will be useful,
1282+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1283+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1284+ * GNU General Public License for more details.
1285+ *
1286+ * You should have received a copy of the GNU General Public License
1287+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1288+ *
1289+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
1290+ */
1291+
1292+#include <miral/workspace_policy.h>
1293+
1294+void miral::WorkspacePolicy::advise_adding_to_workspace(
1295+ std::shared_ptr<Workspace> const& , std::vector<Window> const&)
1296+{
1297+}
1298+
1299+void miral::WorkspacePolicy::advise_removing_from_workspace(
1300+ std::shared_ptr<Workspace> const&, std::vector<Window> const&)
1301+{
1302+}
1303
1304=== modified file 'scripts/process_doxygen_xml.py'
1305--- scripts/process_doxygen_xml.py 2017-02-15 12:06:25 +0000
1306+++ scripts/process_doxygen_xml.py 2017-02-15 17:24:17 +0000
1307@@ -443,10 +443,23 @@
1308
1309 # miral::StartupInternalClient::StartupInternalClient*;
1310 _ZN5miral21StartupInternalClientC?ENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESt8functionIFvN3mir6client10ConnectionEEES7_IFvSt8weak_ptrINS8_5scene7SessionEEEE;
1311+ extern "C++" {
1312+ miral::WindowInfo::shell_chrome*;
1313+ miral::WindowManagerTools::add_tree_to_workspace*;
1314+ miral::WindowManagerTools::create_workspace*;
1315+ miral::WindowManagerTools::drag_window*;
1316+ miral::WindowManagerTools::remove_tree_from_workspace*;
1317+ typeinfo?for?miral::ApplicationAuthorizer1;
1318+ vtable?for?miral::ApplicationAuthorizer1;
1319+ };
1320+} MIRAL_1.1;
1321+
1322+MIRAL_1.3 {
1323+global:
1324 extern "C++" {'''
1325
1326 END_NEW_STANZA = ''' };
1327-} MIRAL_1.1;'''
1328+} MIRAL_1.2;'''
1329
1330 def _print_report():
1331 print OLD_STANZAS
1332
1333=== modified file 'test/CMakeLists.txt'
1334--- test/CMakeLists.txt 2017-02-10 16:48:00 +0000
1335+++ test/CMakeLists.txt 2017-02-15 17:24:17 +0000
1336@@ -56,7 +56,7 @@
1337 display_reconfiguration.cpp
1338 active_window.cpp
1339 raise_tree.cpp
1340-)
1341+ workspaces.cpp)
1342
1343 target_link_libraries(miral-test
1344 ${MIRTEST_LDFLAGS}
1345
1346=== modified file 'test/test_server.cpp'
1347--- test/test_server.cpp 2017-02-14 11:49:59 +0000
1348+++ test/test_server.cpp 2017-02-15 17:24:17 +0000
1349@@ -19,7 +19,6 @@
1350 #include "test_server.h"
1351 #include "../miral/basic_window_manager.h"
1352
1353-#include <miral/canonical_window_manager.h>
1354 #include <miral/set_window_managment_policy.h>
1355
1356 #include <mir_test_framework/executable_path.h>
1357@@ -48,19 +47,13 @@
1358 char const* dummy_args[2] = { "TestServer", nullptr };
1359 }
1360
1361-struct miral::TestServer::TestWindowManagerPolicy : CanonicalWindowManagerPolicy
1362+miral::TestServer::TestWindowManagerPolicy::TestWindowManagerPolicy(
1363+ WindowManagerTools const& tools, TestServer& test_fixture) :
1364+ CanonicalWindowManagerPolicy{tools}
1365 {
1366- TestWindowManagerPolicy(WindowManagerTools const& tools, TestServer& test_fixture) :
1367- CanonicalWindowManagerPolicy{tools}
1368- {
1369- test_fixture.tools = tools;
1370- test_fixture.policy = this;
1371- }
1372-
1373- bool handle_keyboard_event(MirKeyboardEvent const*) override { return false; }
1374- bool handle_pointer_event(MirPointerEvent const*) override { return false; }
1375- bool handle_touch_event(MirTouchEvent const*) override { return false; }
1376-};
1377+ test_fixture.tools = tools;
1378+ test_fixture.policy = this;
1379+}
1380
1381 miral::TestServer::TestServer() :
1382 runner{1, dummy_args}
1383@@ -70,6 +63,12 @@
1384 add_to_environment("MIR_SERVER_NO_FILE", "on");
1385 }
1386
1387+auto miral::TestServer::build_window_manager_policy(WindowManagerTools const& tools)
1388+-> std::unique_ptr<TestWindowManagerPolicy>
1389+{
1390+ return std::make_unique<TestWindowManagerPolicy>(tools, *this);
1391+}
1392+
1393 void miral::TestServer::SetUp()
1394 {
1395 #if MIR_SERVER_VERSION < MIR_VERSION_NUMBER(0, 25, 0)
1396@@ -114,7 +113,7 @@
1397
1398 auto builder = [this](WindowManagerTools const& tools) -> std::unique_ptr<miral::WindowManagementPolicy>
1399 {
1400- return std::make_unique<TestWindowManagerPolicy>(tools, *this);
1401+ return build_window_manager_policy(tools);
1402 };
1403
1404 auto wm = std::make_shared<miral::BasicWindowManager>(focus_controller, display_layout, persistent_surface_store, builder);
1405
1406=== modified file 'test/test_server.h'
1407--- test/test_server.h 2017-02-14 11:49:59 +0000
1408+++ test/test_server.h 2017-02-15 17:24:17 +0000
1409@@ -21,6 +21,7 @@
1410
1411 #include <mir/client/connection.h>
1412
1413+#include <miral/canonical_window_manager.h>
1414 #include <miral/runner.h>
1415 #include <miral/window_manager_tools.h>
1416
1417@@ -63,9 +64,10 @@
1418 void invoke_tools(std::function<void(WindowManagerTools& tools)> const& f);
1419 void invoke_window_manager(std::function<void(mir::shell::WindowManager& wm)> const& f);
1420
1421-private:
1422 struct TestWindowManagerPolicy;
1423+ virtual auto build_window_manager_policy(WindowManagerTools const& tools) -> std::unique_ptr<TestWindowManagerPolicy>;
1424
1425+private:
1426 WindowManagerTools tools{nullptr};
1427 WindowManagementPolicy* policy{nullptr};
1428 std::weak_ptr<mir::shell::WindowManager> window_manager;
1429@@ -74,6 +76,16 @@
1430 std::condition_variable started;
1431 mir::Server* server_running{nullptr};
1432 };
1433+
1434+struct TestServer::TestWindowManagerPolicy : CanonicalWindowManagerPolicy
1435+{
1436+ TestWindowManagerPolicy(WindowManagerTools const& tools, TestServer& test_fixture);
1437+
1438+ bool handle_keyboard_event(MirKeyboardEvent const*) override { return false; }
1439+ bool handle_pointer_event(MirPointerEvent const*) override { return false; }
1440+ bool handle_touch_event(MirTouchEvent const*) override { return false; }
1441+};
1442+
1443 }
1444
1445 #endif //MIRAL_TEST_SERVER_H
1446
1447=== added file 'test/workspaces.cpp'
1448--- test/workspaces.cpp 1970-01-01 00:00:00 +0000
1449+++ test/workspaces.cpp 2017-02-15 17:24:17 +0000
1450@@ -0,0 +1,603 @@
1451+/*
1452+ * Copyright © 2017 Canonical Ltd.
1453+ *
1454+ * This program is free software: you can redistribute it and/or modify it
1455+ * under the terms of the GNU General Public License version 3,
1456+ * as published by the Free Software Foundation.
1457+ *
1458+ * This program is distributed in the hope that it will be useful,
1459+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1460+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1461+ * GNU General Public License for more details.
1462+ *
1463+ * You should have received a copy of the GNU General Public License
1464+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1465+ *
1466+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
1467+ */
1468+
1469+#include <miral/workspace_policy.h>
1470+#include <miral/window_manager_tools.h>
1471+
1472+#include <mir/client/window.h>
1473+#include <mir/client/window_spec.h>
1474+#include <mir_toolkit/mir_buffer_stream.h>
1475+
1476+#include "test_server.h"
1477+
1478+#include <gmock/gmock.h>
1479+#include <mir/test/signal.h>
1480+
1481+
1482+using namespace testing;
1483+using namespace mir::client;
1484+using namespace std::chrono_literals;
1485+using miral::WindowManagerTools;
1486+
1487+namespace
1488+{
1489+#if MIR_CLIENT_VERSION <= MIR_VERSION_NUMBER(3, 4, 0)
1490+auto const mir_window_get_buffer_stream = mir_surface_get_buffer_stream;
1491+auto const mir_window_set_state = mir_surface_set_state;
1492+#endif
1493+
1494+std::string const top_level{"top level"};
1495+std::string const dialog{"dialog"};
1496+std::string const tip{"tip"};
1497+std::string const a_window{"a window"};
1498+std::string const another_window{"another window"};
1499+
1500+struct Workspaces;
1501+
1502+struct WorkspacesWindowManagerPolicy : miral::TestServer::TestWindowManagerPolicy, miral::WorkspacePolicy
1503+{
1504+ WorkspacesWindowManagerPolicy(WindowManagerTools const& tools, Workspaces& test_fixture);
1505+ ~WorkspacesWindowManagerPolicy();
1506+
1507+ void advise_new_window(miral::WindowInfo const& window_info) override;
1508+
1509+ MOCK_METHOD2(advise_adding_to_workspace,
1510+ void(std::shared_ptr<miral::Workspace> const&, std::vector<miral::Window> const&));
1511+
1512+ MOCK_METHOD2(advise_removing_from_workspace,
1513+ void(std::shared_ptr<miral::Workspace> const&, std::vector<miral::Window> const&));
1514+
1515+ MOCK_METHOD1(advise_focus_gained, void(miral::WindowInfo const&));
1516+
1517+ Workspaces& test_fixture;
1518+};
1519+
1520+struct Workspaces : public miral::TestServer
1521+{
1522+ auto create_window(std::string const& name) -> Window
1523+ {
1524+ auto const window = WindowSpec::for_normal_window(client_connection, 50, 50, mir_pixel_format_argb_8888)
1525+ .set_buffer_usage(mir_buffer_usage_software)
1526+ .set_name(name.c_str())
1527+ .create_window();
1528+
1529+ client_windows[name] = window;
1530+ mir_buffer_stream_swap_buffers_sync(mir_window_get_buffer_stream(window));
1531+
1532+ return window;
1533+ }
1534+
1535+ auto create_tip(std::string const& name, Window const& parent) -> Window
1536+ {
1537+ MirRectangle aux_rect{10, 10, 10, 10};
1538+ auto const window = WindowSpec::for_tip(client_connection, 50, 50, mir_pixel_format_argb_8888, parent,
1539+ &aux_rect, mir_edge_attachment_any)
1540+ .set_buffer_usage(mir_buffer_usage_software)
1541+ .set_name(name.c_str())
1542+ .create_window();
1543+
1544+ client_windows[name] = window;
1545+ mir_buffer_stream_swap_buffers_sync(mir_window_get_buffer_stream(window));
1546+
1547+ return window;
1548+ }
1549+
1550+ auto create_dialog(std::string const& name, Window const& parent) -> Window
1551+ {
1552+ auto const window = WindowSpec::for_dialog(client_connection, 50, 50, mir_pixel_format_argb_8888, parent)
1553+ .set_buffer_usage(mir_buffer_usage_software)
1554+ .set_name(name.c_str())
1555+ .create_window();
1556+
1557+ client_windows[name] = window;
1558+ mir_buffer_stream_swap_buffers_sync(mir_window_get_buffer_stream(window));
1559+
1560+ return window;
1561+ }
1562+
1563+ auto create_workspace() -> std::shared_ptr<miral::Workspace>
1564+ {
1565+ std::shared_ptr<miral::Workspace> result;
1566+
1567+ invoke_tools([&](WindowManagerTools& tools)
1568+ { result = tools.create_workspace(); });
1569+
1570+ return result;
1571+ }
1572+
1573+ void SetUp() override
1574+ {
1575+ miral::TestServer::SetUp();
1576+ EXPECT_CALL(policy(), advise_adding_to_workspace(_, _)).Times(AnyNumber());
1577+ EXPECT_CALL(policy(), advise_removing_from_workspace(_, _)).Times(AnyNumber());
1578+ EXPECT_CALL(policy(), advise_focus_gained(_)).Times(AnyNumber());
1579+
1580+ client_connection = connect_client("Workspaces");
1581+ create_window(top_level);
1582+ create_dialog(dialog, client_windows[top_level]);
1583+ create_tip(tip, client_windows[dialog]);
1584+
1585+ EXPECT_THAT(client_windows.size(), Eq(3u));
1586+ EXPECT_THAT(server_windows.size(), Eq(3u));
1587+ }
1588+
1589+ void TearDown() override
1590+ {
1591+ client_windows.clear();
1592+ client_connection.reset();
1593+ miral::TestServer::TearDown();
1594+ }
1595+
1596+ Connection client_connection;
1597+
1598+ auto server_window(std::string const& key) -> miral::Window
1599+ {
1600+ std::lock_guard<decltype(mutex)> lock{mutex};
1601+ return server_windows[key];
1602+ }
1603+
1604+ auto client_window(std::string const& key) -> Window&
1605+ {
1606+ return client_windows[key];
1607+ }
1608+
1609+ auto windows_in_workspace(std::shared_ptr<miral::Workspace> const& workspace) -> std::vector<miral::Window>
1610+ {
1611+ std::vector<miral::Window> result;
1612+
1613+ auto enumerate = [&result](miral::Window const& window)
1614+ {
1615+ result.push_back(window);
1616+ };
1617+
1618+ invoke_tools([&](WindowManagerTools& tools)
1619+ { tools.for_each_window_in_workspace(workspace, enumerate); });
1620+
1621+ return result;
1622+ }
1623+
1624+ auto workspaces_containing_window(miral::Window const& window) -> std::vector<std::shared_ptr<miral::Workspace>>
1625+ {
1626+ std::vector<std::shared_ptr<miral::Workspace>> result;
1627+
1628+ auto enumerate = [&result](std::shared_ptr<miral::Workspace> const& workspace)
1629+ {
1630+ result.push_back(workspace);
1631+ };
1632+
1633+ invoke_tools([&](WindowManagerTools& tools)
1634+ { tools.for_each_workspace_containing(window, enumerate); });
1635+
1636+ return result;
1637+ }
1638+
1639+ auto policy() -> WorkspacesWindowManagerPolicy&
1640+ {
1641+ if (!the_policy) throw std::logic_error("the_policy isn't valid");
1642+ return *the_policy;
1643+ }
1644+
1645+private:
1646+ std::mutex mutable mutex;
1647+ std::map<std::string, Window> client_windows;
1648+ std::map<std::string, miral::Window> server_windows;
1649+ WorkspacesWindowManagerPolicy* the_policy{nullptr};
1650+
1651+ friend struct WorkspacesWindowManagerPolicy;
1652+
1653+ auto build_window_manager_policy(WindowManagerTools const& tools)
1654+ -> std::unique_ptr<TestWindowManagerPolicy> override
1655+ {
1656+ return std::make_unique<WorkspacesWindowManagerPolicy>(tools, *this);
1657+ }
1658+};
1659+
1660+WorkspacesWindowManagerPolicy::WorkspacesWindowManagerPolicy(WindowManagerTools const& tools, Workspaces& test_fixture) :
1661+TestWindowManagerPolicy(tools, test_fixture), test_fixture{test_fixture}
1662+{
1663+ test_fixture.the_policy = this;
1664+}
1665+
1666+WorkspacesWindowManagerPolicy::~WorkspacesWindowManagerPolicy()
1667+{
1668+ test_fixture.the_policy = nullptr;
1669+}
1670+
1671+
1672+void WorkspacesWindowManagerPolicy::advise_new_window(miral::WindowInfo const& window_info)
1673+{
1674+ miral::TestServer::TestWindowManagerPolicy::advise_new_window(window_info);
1675+
1676+ std::lock_guard<decltype(test_fixture.mutex)> lock{test_fixture.mutex};
1677+ test_fixture.server_windows[window_info.name()] = window_info.window();
1678+}
1679+}
1680+
1681+TEST_F(Workspaces, before_a_tree_is_added_to_workspace_it_is_empty)
1682+{
1683+ auto const workspace = create_workspace();
1684+
1685+ EXPECT_THAT(windows_in_workspace(workspace).size(), Eq(0u));
1686+}
1687+
1688+TEST_F(Workspaces, when_a_tree_is_added_to_workspace_all_surfaces_in_tree_are_added)
1689+{
1690+ auto const workspace = create_workspace();
1691+ invoke_tools([&, this](WindowManagerTools& tools)
1692+ { tools.add_tree_to_workspace(server_window(dialog), workspace); });
1693+
1694+ EXPECT_THAT(windows_in_workspace(workspace).size(), Eq(3u));
1695+}
1696+
1697+TEST_F(Workspaces, when_a_tree_is_removed_from_workspace_all_surfaces_in_tree_are_removed)
1698+{
1699+ auto const workspace = create_workspace();
1700+ invoke_tools([&, this](WindowManagerTools& tools)
1701+ { tools.add_tree_to_workspace(server_window(dialog), workspace); });
1702+
1703+ invoke_tools([&, this](WindowManagerTools& tools)
1704+ { tools.remove_tree_from_workspace(server_window(tip), workspace); });
1705+
1706+ EXPECT_THAT(windows_in_workspace(workspace).size(), Eq(0u));
1707+}
1708+
1709+TEST_F(Workspaces, given_a_tree_in_a_workspace_when_another_tree_is_added_and_removed_from_workspace_the_original_tree_remains)
1710+{
1711+ auto const workspace = create_workspace();
1712+ auto const original_tree = "original_tree";
1713+ auto const client_window = create_window(original_tree);
1714+ auto const original_window= server_window(original_tree);
1715+
1716+ invoke_tools([&, this](WindowManagerTools& tools)
1717+ { tools.add_tree_to_workspace(original_window, workspace); });
1718+
1719+ invoke_tools([&, this](WindowManagerTools& tools)
1720+ { tools.add_tree_to_workspace(server_window(top_level), workspace); });
1721+ invoke_tools([&, this](WindowManagerTools& tools)
1722+ { tools.remove_tree_from_workspace(server_window(top_level), workspace); });
1723+
1724+ EXPECT_THAT(windows_in_workspace(workspace), ElementsAre(original_window));
1725+}
1726+
1727+TEST_F(Workspaces, when_a_tree_is_added_to_a_workspace_all_surfaces_are_contained_in_the_workspace)
1728+{
1729+ auto const workspace = create_workspace();
1730+ invoke_tools([&, this](WindowManagerTools& tools)
1731+ { tools.add_tree_to_workspace(server_window(dialog), workspace); });
1732+
1733+ EXPECT_THAT(workspaces_containing_window(server_window(top_level)), ElementsAre(workspace));
1734+ EXPECT_THAT(workspaces_containing_window(server_window(dialog)), ElementsAre(workspace));
1735+ EXPECT_THAT(workspaces_containing_window(server_window(tip)), ElementsAre(workspace));
1736+}
1737+
1738+
1739+TEST_F(Workspaces, when_a_tree_is_added_to_a_workspaces_twice_surfaces_are_contained_in_one_workspace)
1740+{
1741+ auto const workspace = create_workspace();
1742+ invoke_tools([&, this](WindowManagerTools& tools)
1743+ {
1744+ tools.add_tree_to_workspace(server_window(dialog), workspace);
1745+ tools.add_tree_to_workspace(server_window(dialog), workspace);
1746+ });
1747+
1748+ EXPECT_THAT(workspaces_containing_window(server_window(top_level)), ElementsAre(workspace));
1749+ EXPECT_THAT(workspaces_containing_window(server_window(dialog)), ElementsAre(workspace));
1750+ EXPECT_THAT(workspaces_containing_window(server_window(tip)), ElementsAre(workspace));
1751+
1752+ EXPECT_THAT(workspaces_containing_window(server_window(top_level)).size(), Eq(1u));
1753+ EXPECT_THAT(workspaces_containing_window(server_window(dialog)).size(), Eq(1u));
1754+ EXPECT_THAT(workspaces_containing_window(server_window(tip)).size(), Eq(1u));
1755+}
1756+
1757+TEST_F(Workspaces, when_a_tree_is_added_to_two_workspaces_all_surfaces_are_contained_in_two_workspaces)
1758+{
1759+ auto const workspace1 = create_workspace();
1760+ auto const workspace2 = create_workspace();
1761+ invoke_tools([&, this](WindowManagerTools& tools)
1762+ {
1763+ tools.add_tree_to_workspace(server_window(dialog), workspace1);
1764+ tools.add_tree_to_workspace(server_window(dialog), workspace2);
1765+ });
1766+
1767+ EXPECT_THAT(workspaces_containing_window(server_window(top_level)).size(), Eq(2u));
1768+ EXPECT_THAT(workspaces_containing_window(server_window(dialog)).size(), Eq(2u));
1769+ EXPECT_THAT(workspaces_containing_window(server_window(tip)).size(), Eq(2u));
1770+}
1771+
1772+TEST_F(Workspaces, when_workspace_is_closed_surfaces_are_no_longer_contained_in_it)
1773+{
1774+ auto const workspace1 = create_workspace();
1775+ auto workspace2 = create_workspace();
1776+ invoke_tools([&, this](WindowManagerTools& tools)
1777+ {
1778+ tools.add_tree_to_workspace(server_window(dialog), workspace1);
1779+ tools.add_tree_to_workspace(server_window(dialog), workspace2);
1780+ });
1781+
1782+ workspace2.reset();
1783+
1784+ EXPECT_THAT(workspaces_containing_window(server_window(top_level)), ElementsAre(workspace1));
1785+ EXPECT_THAT(workspaces_containing_window(server_window(dialog)), ElementsAre(workspace1));
1786+ EXPECT_THAT(workspaces_containing_window(server_window(tip)), ElementsAre(workspace1));
1787+
1788+ EXPECT_THAT(workspaces_containing_window(server_window(top_level)).size(), Eq(1u));
1789+ EXPECT_THAT(workspaces_containing_window(server_window(dialog)).size(), Eq(1u));
1790+ EXPECT_THAT(workspaces_containing_window(server_window(tip)).size(), Eq(1u));
1791+}
1792+
1793+TEST_F(Workspaces, when_a_tree_is_added_to_a_workspace_the_policy_is_notified)
1794+{
1795+ auto const workspace = create_workspace();
1796+
1797+ EXPECT_CALL(policy(), advise_adding_to_workspace(workspace,
1798+ ElementsAre(server_window(top_level), server_window(dialog), server_window(tip))));
1799+
1800+ invoke_tools([&, this](WindowManagerTools& tools)
1801+ { tools.add_tree_to_workspace(server_window(dialog), workspace); });
1802+}
1803+
1804+TEST_F(Workspaces, when_a_tree_is_added_to_a_workspaces_twice_the_policy_is_notified_once)
1805+{
1806+ auto const workspace = create_workspace();
1807+
1808+ EXPECT_CALL(policy(), advise_adding_to_workspace(workspace,
1809+ ElementsAre(server_window(top_level), server_window(dialog), server_window(tip))));
1810+
1811+ invoke_tools([&, this](WindowManagerTools& tools)
1812+ {
1813+ tools.add_tree_to_workspace(server_window(dialog), workspace);
1814+ tools.add_tree_to_workspace(server_window(dialog), workspace);
1815+ });
1816+}
1817+
1818+TEST_F(Workspaces, when_a_tree_is_removed_from_a_workspace_the_policy_is_notified)
1819+{
1820+ auto const workspace = create_workspace();
1821+ invoke_tools([&, this](WindowManagerTools& tools)
1822+ { tools.add_tree_to_workspace(server_window(dialog), workspace); });
1823+
1824+ EXPECT_CALL(policy(), advise_removing_from_workspace(workspace,
1825+ ElementsAre(server_window(top_level), server_window(dialog), server_window(tip))));
1826+
1827+ invoke_tools([&, this](WindowManagerTools& tools)
1828+ { tools.remove_tree_from_workspace(server_window(tip), workspace); });
1829+}
1830+
1831+TEST_F(Workspaces, when_a_tree_is_removed_from_a_workspace_twice_the_policy_is_notified_once)
1832+{
1833+ auto const workspace = create_workspace();
1834+ invoke_tools([&, this](WindowManagerTools& tools)
1835+ { tools.add_tree_to_workspace(server_window(dialog), workspace); });
1836+
1837+ EXPECT_CALL(policy(), advise_removing_from_workspace(workspace,
1838+ ElementsAre(server_window(top_level), server_window(dialog), server_window(tip))));
1839+
1840+ invoke_tools([&, this](WindowManagerTools& tools)
1841+ {
1842+ tools.remove_tree_from_workspace(server_window(top_level), workspace);
1843+ tools.remove_tree_from_workspace(server_window(tip), workspace);
1844+ });
1845+}
1846+
1847+TEST_F(Workspaces, a_child_window_is_added_to_workspace_of_parent)
1848+{
1849+ auto const workspace = create_workspace();
1850+ invoke_tools([&, this](WindowManagerTools& tools)
1851+ { tools.add_tree_to_workspace(server_window(dialog), workspace); });
1852+
1853+ EXPECT_CALL(policy(), advise_adding_to_workspace(workspace, ElementsAre(_)));
1854+
1855+ create_dialog(a_window, client_window(top_level));
1856+}
1857+
1858+TEST_F(Workspaces, a_closing_window_is_removed_from_workspace)
1859+{
1860+ auto const workspace = create_workspace();
1861+ invoke_tools([&, this](WindowManagerTools& tools)
1862+ { tools.add_tree_to_workspace(server_window(dialog), workspace); });
1863+
1864+ create_dialog(a_window, client_window(dialog));
1865+
1866+ EXPECT_CALL(policy(), advise_removing_from_workspace(workspace, ElementsAre(server_window(a_window))));
1867+
1868+ client_window(a_window).reset();
1869+}
1870+
1871+TEST_F(Workspaces, when_a_window_in_a_workspace_closes_focus_remains_in_workspace)
1872+{
1873+ auto const workspace = create_workspace();
1874+
1875+ create_window(a_window);
1876+ create_window(another_window);
1877+
1878+ invoke_tools([&, this](WindowManagerTools& tools)
1879+ {
1880+ tools.add_tree_to_workspace(server_window(a_window), workspace);
1881+ tools.add_tree_to_workspace(server_window(another_window), workspace);
1882+
1883+ tools.select_active_window(server_window(dialog));
1884+ tools.select_active_window(server_window(a_window));
1885+ });
1886+
1887+ client_window(a_window).reset();
1888+
1889+ invoke_tools([&, this](WindowManagerTools& tools)
1890+ {
1891+ EXPECT_THAT(tools.active_window(), Eq(server_window(another_window)))
1892+ << "tools.active_window() . . . .: " << tools.info_for(tools.active_window()).name() << "\n"
1893+ << "server_window(another_window): " << tools.info_for(server_window(another_window)).name();
1894+ });
1895+}
1896+
1897+TEST_F(Workspaces, with_two_applications_when_a_window_in_a_workspace_closes_focus_remains_in_workspace)
1898+{
1899+ auto const workspace = create_workspace();
1900+
1901+ create_window(another_window);
1902+
1903+ {
1904+ auto const another_app = connect_client("another app");
1905+ auto const window = WindowSpec::for_normal_window(another_app, 50, 50, mir_pixel_format_argb_8888)
1906+ .set_buffer_usage(mir_buffer_usage_software)
1907+ .set_name(a_window.c_str())
1908+ .create_window();
1909+
1910+ mir_buffer_stream_swap_buffers_sync(mir_window_get_buffer_stream(window));
1911+
1912+ invoke_tools([&, this](WindowManagerTools& tools)
1913+ {
1914+ tools.add_tree_to_workspace(server_window(top_level), workspace);
1915+ tools.add_tree_to_workspace(server_window(a_window), workspace);
1916+ });
1917+ }
1918+
1919+ invoke_tools([&, this](WindowManagerTools& tools)
1920+ {
1921+ EXPECT_THAT(tools.active_window(), Eq(server_window(dialog)))
1922+ << "tools.active_window(): " << tools.info_for(tools.active_window()).name() << "\n"
1923+ << "server_window(dialog): " << tools.info_for(server_window(dialog)).name();
1924+ });
1925+}
1926+
1927+TEST_F(Workspaces, when_a_window_in_a_workspace_hides_focus_remains_in_workspace)
1928+{
1929+ auto const workspace = create_workspace();
1930+
1931+ create_window(a_window);
1932+ create_window(another_window);
1933+
1934+ invoke_tools([&, this](WindowManagerTools& tools)
1935+ {
1936+ tools.add_tree_to_workspace(server_window(a_window), workspace);
1937+ tools.add_tree_to_workspace(server_window(another_window), workspace);
1938+
1939+ tools.select_active_window(server_window(dialog));
1940+ tools.select_active_window(server_window(a_window));
1941+ });
1942+
1943+ mir::test::Signal focus_changed;
1944+ EXPECT_CALL(policy(), advise_focus_gained(_)).WillOnce(InvokeWithoutArgs([&]{ focus_changed.raise(); }));
1945+
1946+ mir_window_set_state(client_window(a_window), mir_window_state_hidden);
1947+
1948+ EXPECT_TRUE(focus_changed.wait_for(1s));
1949+
1950+ invoke_tools([&, this](WindowManagerTools& tools)
1951+ {
1952+ EXPECT_THAT(tools.active_window(), Eq(server_window(another_window)))
1953+ << "tools.active_window() . . . .: " << tools.info_for(tools.active_window()).name() << "\n"
1954+ << "server_window(another_window): " << tools.info_for(server_window(another_window)).name();
1955+ });
1956+}
1957+
1958+
1959+TEST_F(Workspaces, with_two_applications_when_a_window_in_a_workspace_hides_focus_remains_in_workspace)
1960+{
1961+ auto const workspace = create_workspace();
1962+
1963+ create_window(another_window);
1964+
1965+ auto const another_app = connect_client("another app");
1966+ auto const window = WindowSpec::for_normal_window(another_app, 50, 50, mir_pixel_format_argb_8888)
1967+ .set_buffer_usage(mir_buffer_usage_software)
1968+ .set_name(a_window.c_str())
1969+ .create_window();
1970+
1971+ mir_buffer_stream_swap_buffers_sync(mir_window_get_buffer_stream(window));
1972+
1973+ invoke_tools([&, this](WindowManagerTools& tools)
1974+ {
1975+ tools.add_tree_to_workspace(server_window(top_level), workspace);
1976+ tools.add_tree_to_workspace(server_window(a_window), workspace);
1977+ });
1978+
1979+
1980+ mir::test::Signal focus_changed;
1981+ EXPECT_CALL(policy(), advise_focus_gained(_)).WillOnce(InvokeWithoutArgs([&]{ focus_changed.raise(); }));
1982+
1983+ mir_window_set_state(window, mir_window_state_hidden);
1984+
1985+ EXPECT_TRUE(focus_changed.wait_for(1s));
1986+
1987+ invoke_tools([&, this](WindowManagerTools& tools)
1988+ {
1989+ EXPECT_THAT(tools.active_window(), Eq(server_window(dialog)))
1990+ << "tools.active_window(): " << tools.info_for(tools.active_window()).name() << "\n"
1991+ << "server_window(dialog): " << tools.info_for(server_window(dialog)).name();
1992+ });
1993+
1994+ Mock::VerifyAndClearExpectations(&policy()); // before shutdown
1995+}
1996+
1997+TEST_F(Workspaces, focus_next_within_application_keeps_focus_in_workspace)
1998+{
1999+ auto const workspace = create_workspace();
2000+
2001+ create_window(another_window);
2002+ create_window(a_window);
2003+
2004+ invoke_tools([&, this](WindowManagerTools& tools)
2005+ {
2006+ tools.add_tree_to_workspace(server_window(a_window), workspace);
2007+ tools.add_tree_to_workspace(server_window(dialog), workspace);
2008+
2009+ tools.focus_next_within_application();
2010+
2011+ EXPECT_THAT(tools.active_window(), Eq(server_window(dialog)))
2012+ << "tools.active_window(): " << tools.info_for(tools.active_window()).name() << "\n"
2013+ << "server_window(dialog): " << tools.info_for(server_window(dialog)).name();
2014+
2015+ tools.focus_next_within_application();
2016+
2017+ EXPECT_THAT(tools.active_window(), Eq(server_window(a_window)))
2018+ << "tools.active_window(). : " << tools.info_for(tools.active_window()).name() << "\n"
2019+ << "server_window(a_window): " << tools.info_for(server_window(a_window)).name();
2020+ });
2021+}
2022+
2023+TEST_F(Workspaces, focus_next_application_keeps_focus_in_workspace)
2024+{
2025+ auto const workspace = create_workspace();
2026+ create_window(another_window);
2027+
2028+ auto const another_app = connect_client("another app");
2029+ auto const window = WindowSpec::for_normal_window(another_app, 50, 50, mir_pixel_format_argb_8888)
2030+ .set_buffer_usage(mir_buffer_usage_software)
2031+ .set_name(a_window.c_str())
2032+ .create_window();
2033+
2034+ mir_buffer_stream_swap_buffers_sync(mir_window_get_buffer_stream(window));
2035+
2036+ invoke_tools([&, this](WindowManagerTools& tools)
2037+ {
2038+ tools.add_tree_to_workspace(server_window(top_level), workspace);
2039+ tools.add_tree_to_workspace(server_window(a_window), workspace);
2040+
2041+ tools.focus_next_application();
2042+
2043+ EXPECT_THAT(tools.active_window(), Eq(server_window(dialog)))
2044+ << "tools.active_window(): " << tools.info_for(tools.active_window()).name() << "\n"
2045+ << "server_window(dialog): " << tools.info_for(server_window(dialog)).name();
2046+
2047+ tools.focus_next_application();
2048+
2049+ EXPECT_THAT(tools.active_window(), Eq(server_window(a_window)))
2050+ << "tools.active_window(). : " << tools.info_for(tools.active_window()).name() << "\n"
2051+ << "server_window(a_window): " << tools.info_for(server_window(a_window)).name();
2052+ });
2053+}

Subscribers

People subscribed via source and target branches