Merge lp:~alan-griffiths/miral/1.3 into lp:miral/release

Proposed by Alan Griffiths
Status: Merged
Approved by: Alan Griffiths
Approved revision: no longer in the source branch.
Merged at revision: 355
Proposed branch: lp:~alan-griffiths/miral/1.3
Merge into: lp:miral/release
Diff against target: 4243 lines (+2490/-256)
44 files modified
CMakeLists.txt (+1/-1)
debian/changelog (+23/-0)
debian/control (+3/-5)
debian/libmiral2.symbols (+15/-2)
include/mir/client/connection.h (+3/-0)
include/mir/client/display_config.h (+83/-0)
include/mir/client/window.h (+4/-0)
include/mir/client/window_id.h (+5/-6)
include/mir/client/window_spec.h (+4/-0)
include/miral/window.h (+5/-0)
include/miral/window_manager_tools.h (+67/-1)
include/miral/workspace_policy.h (+88/-0)
miral-kiosk/kiosk_window_manager.cpp (+8/-0)
miral-kiosk/sw_splash.cpp (+4/-8)
miral-shell/CMakeLists.txt (+1/-1)
miral-shell/decoration_provider.cpp (+204/-51)
miral-shell/decoration_provider.h (+16/-12)
miral-shell/shell_main.cpp (+6/-2)
miral-shell/tiling_window_manager.cpp (+8/-0)
miral-shell/titlebar_window_manager.cpp (+212/-31)
miral-shell/titlebar_window_manager.h (+32/-5)
miral/CMakeLists.txt (+12/-3)
miral/basic_window_manager.cpp (+507/-58)
miral/basic_window_manager.h (+49/-1)
miral/internal_client.cpp (+61/-9)
miral/join_client_threads.h (+24/-0)
miral/mir_features.h.in (+1/-0)
miral/mru_window_list.cpp (+25/-3)
miral/runner.cpp (+11/-2)
miral/symbols.map (+23/-0)
miral/window.cpp (+5/-0)
miral/window_management_trace.cpp (+89/-9)
miral/window_management_trace.h (+18/-0)
miral/window_manager_tools.cpp (+31/-0)
miral/window_manager_tools_implementation.h (+15/-0)
miral/workspace_policy.cpp (+29/-0)
scripts/process_doxygen_xml.py (+11/-1)
test/CMakeLists.txt (+1/-1)
test/active_window.cpp (+8/-25)
test/mru_window_list.cpp (+58/-4)
test/test_server.cpp (+13/-14)
test/test_server.h (+13/-1)
test/window_properties.cpp (+25/-0)
test/workspaces.cpp (+669/-0)
To merge this branch: bzr merge lp:~alan-griffiths/miral/1.3
Reviewer Review Type Date Requested Status
Mir development team Pending
Review via email: mp+318889@code.launchpad.net

Commit message

1.3 release

To post a comment you must log in.
lp:~alan-griffiths/miral/1.3 updated
355. By CI Train Bot Account

* New upstream release 1.3.0 (https://launchpad.net/miral/+milestone/1.3)
  - ABI summary:
    . miral ABI unchanged at 2
  - Enhancements:
    . [libmiral] Support for workspaces
    . [libmiral] Add miral::WindowManagerTools::focus_prev_within_application
                 (LP: #1668562)
    . [libmirclientcpp] A better description of libmirclientcpp-dev
    . [libmirclientcpp] Add RAII wrapper for MirDisplayConfig.
    . [libmirclientcpp] Prevent accidental double-release by deleting release
                        functions for handle classes
    . [miral-shell] Example workspaces implementation
    . [miral-shell] Use background to show keyboard shortcuts
  - Bugs fixed:
    . [libmiral] Join internal client threads before server shutdown
                 (LP: #1668651)
    . [miral-shell] Workaround for crash on exit (LP: #1667645)

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-03-03 10:23:15 +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-15 14:05:46 +0000
16+++ debian/changelog 2017-03-03 10:23:15 +0000
17@@ -1,3 +1,26 @@
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+ . [libmiral] Support for workspaces
26+ . [libmiral] Add miral::WindowManagerTools::focus_prev_within_application
27+ (LP: #1668562)
28+ . [libmirclientcpp] A better description of libmirclientcpp-dev
29+ . [libmirclientcpp] Add RAII wrapper for MirDisplayConfig.
30+ . [libmirclientcpp] Prevent accidental double-release by deleting release
31+ functions for handle classes
32+ . [miral-shell] Example workspaces implementation
33+ . [miral-shell] Use background to show keyboard shortcuts
34+ - Bugs fixed:
35+ . [libmiral] Join internal client threads before server shutdown
36+ (LP: #1668651)
37+ . [miral-shell] Workaround for crash on exit (LP: #1667645)
38+
39+ -- Alan Griffiths <alan.griffiths@canonical.com> Thu, 02 Feb 2017 17:26:54 +0000
40+
41 miral (1.2.0+17.04.20170215.1-0ubuntu1) zesty; urgency=medium
42
43 * New upstream release 1.2.0 (https://launchpad.net/miral/+milestone/1.2)
44
45=== modified file 'debian/control'
46--- debian/control 2017-02-14 12:17:33 +0000
47+++ debian/control 2017-03-03 10:23:15 +0000
48@@ -66,12 +66,10 @@
49 Multi-Arch: same
50 Pre-Depends: ${misc:Pre-Depends}
51 Depends: libmirclient-dev,
52-Description: Developer files for the Mir ABI-stable abstraction layer
53- MirAL provides an ABI-stable abstraction layer for Mir based shells,
54- insulating them from mirserver ABI breaks.
55+Description: A C++ wrapper for libmirclient-dev
56+ Provides RAII (and other facilities) for Mir client library types.
57 .
58- Contains header files required for development using the MirAL abstraction
59- layer.
60+ Contains header files useful for C++ development against Mir.
61
62 Package: miral-examples
63 Architecture: linux-any
64
65=== modified file 'debian/libmiral2.symbols'
66--- debian/libmiral2.symbols 2017-02-14 11:49:59 +0000
67+++ debian/libmiral2.symbols 2017-03-03 10:23:15 +0000
68@@ -1,7 +1,5 @@
69 libmiral.so.2 libmiral2 #MINVER#
70 MIRAL_1.0@MIRAL_1.0 1.0.0
71- (c++)"miral::WindowInfo::shell_chrome(MirShellChrome)@MIRAL_1.2" 1.2.0
72- (c++)"miral::WindowInfo::shell_chrome() const@MIRAL_1.2" 1.2.0
73 (c++)"miral::WindowInfo::height_inc(mir::geometry::detail::IntWrapper<mir::geometry::DeltaYTag>)@MIRAL_1.0" 1.0.0
74 (c++)"miral::WindowInfo::max_aspect(miral::WindowSpecification::AspectRatio)@MIRAL_1.0" 1.0.0
75 (c++)"miral::WindowInfo::max_height(mir::geometry::detail::IntWrapper<mir::geometry::HeightTag>)@MIRAL_1.0" 1.0.0
76@@ -367,6 +365,21 @@
77 MIRAL_1.2@MIRAL_1.2 1.2.0
78 (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
79 (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
80+ (c++)"miral::WindowInfo::shell_chrome(MirShellChrome)@MIRAL_1.2" 1.2.0
81+ (c++)"miral::WindowInfo::shell_chrome() const@MIRAL_1.2" 1.2.0
82 (c++)"miral::WindowManagerTools::drag_window(miral::Window const&, mir::geometry::Displacement)@MIRAL_1.2" 1.2.0
83 (c++)"typeinfo for miral::ApplicationAuthorizer@MIRAL_1.0" 1.2.0
84 (c++)"typeinfo for miral::ApplicationAuthorizer1@MIRAL_1.2" 1.2.0
85+ MIRAL_1.3@MIRAL_1.3 1.3.0
86+ (c++)"miral::operator<(miral::Window const&, miral::Window const&)@MIRAL_1.0" 1.3.0
87+ (c++)"miral::WindowManagerTools::create_workspace()@MIRAL_1.3" 1.3.0
88+ (c++)"miral::WindowManagerTools::add_tree_to_workspace(miral::Window const&, std::shared_ptr<miral::Workspace> const&)@MIRAL_1.3" 1.3.0
89+ (c++)"miral::WindowManagerTools::focus_prev_within_application()@MIRAL_1.3" 1.3.0
90+ (c++)"miral::WindowManagerTools::remove_tree_from_workspace(miral::Window const&, std::shared_ptr<miral::Workspace> const&)@MIRAL_1.3" 1.3.0
91+ (c++)"miral::WindowManagerTools::move_workspace_content_to_workspace(std::shared_ptr<miral::Workspace> const&, std::shared_ptr<miral::Workspace> const&)@MIRAL_1.3" 1.3.0
92+ (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.3.0
93+ (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.3.0
94+ (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.3.0
95+ (c++)"miral::WindowManagerTools::for_each_workspace_containing(miral::Window const&, std::function<void (std::shared_ptr<miral::Workspace> const&)> const&)@MIRAL_1.3" 1.3.0
96+ (c++)"typeinfo for miral::WorkspacePolicy@MIRAL_1.3" 1.3.0
97+ (c++)"vtable for miral::WorkspacePolicy@MIRAL_1.3" 1.3.0
98
99=== modified file 'include/mir/client/connection.h'
100--- include/mir/client/connection.h 2017-02-14 13:50:07 +0000
101+++ include/mir/client/connection.h 2017-03-03 10:23:15 +0000
102@@ -45,6 +45,9 @@
103 static void deleter(MirConnection* connection) { mir_connection_release(connection); }
104 std::shared_ptr<MirConnection> self;
105 };
106+
107+// Provide a deleted overload to avoid double release "accidents".
108+void mir_connection_release(Connection const& connection) = delete;
109 }
110 }
111
112
113=== added file 'include/mir/client/display_config.h'
114--- include/mir/client/display_config.h 1970-01-01 00:00:00 +0000
115+++ include/mir/client/display_config.h 2017-03-03 10:23:15 +0000
116@@ -0,0 +1,83 @@
117+/*
118+ * Copyright © 2017 Canonical Ltd.
119+ *
120+ * This program is free software: you can redistribute it and/or modify it
121+ * under the terms of the GNU General Public License version 3,
122+ * as published by the Free Software Foundation.
123+ *
124+ * This program is distributed in the hope that it will be useful,
125+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
126+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
127+ * GNU General Public License for more details.
128+ *
129+ * You should have received a copy of the GNU General Public License
130+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
131+ *
132+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
133+ */
134+
135+#ifndef MIR_CLIENT_DISPLAY_CONFIG_H
136+#define MIR_CLIENT_DISPLAY_CONFIG_H
137+
138+#include <mir_toolkit/mir_connection.h>
139+#include <mir_toolkit/mir_display_configuration.h>
140+#include <mir/client/detail/mir_features.h>
141+
142+#include <functional>
143+#include <memory>
144+
145+namespace mir
146+{
147+/// Convenient C++ wrappers around the Mir toolkit API.
148+///
149+/// These wrappers are intentionally inline adapters: the compiled code depend directly on the Mir toolkit API.
150+namespace client
151+{
152+class DisplayConfig
153+{
154+public:
155+ DisplayConfig() = default;
156+
157+ explicit DisplayConfig(MirDisplayConfig* config) : self{config, deleter} {}
158+
159+ explicit DisplayConfig(MirConnection* connection) :
160+ self{mir_connection_create_display_configuration(connection), deleter} {}
161+
162+ operator MirDisplayConfig*() { return self.get(); }
163+
164+ operator MirDisplayConfig const*() const { return self.get(); }
165+
166+ void reset() { self.reset(); }
167+
168+ void for_each_output(std::function<void(MirOutput const*)> const& enumerator) const
169+ {
170+ auto const count = mir_display_config_get_num_outputs(*this);
171+
172+ for (int i = 0; i != count; ++i)
173+ enumerator(mir_display_config_get_output(*this, i));
174+ }
175+
176+#if MIR_DEFINES_DISPLAY_CONFIG_GET_MUTABLE_OUTPUT
177+ // Is it worthwhile to emulate this functionality for Mir 0.21?
178+ // Too many API gaps I think.
179+ void for_each_output(std::function<void(MirOutput*)> const& enumerator)
180+ {
181+ auto const count = mir_display_config_get_num_outputs(*this);
182+
183+ for (int i = 0; i != count; ++i)
184+ enumerator(mir_display_config_get_mutable_output(*this, i));
185+ }
186+#endif
187+
188+private:
189+ static void deleter(MirDisplayConfig* config) { mir_display_config_release(config); }
190+
191+ std::shared_ptr <MirDisplayConfig> self;
192+};
193+
194+// Provide a deleted overload to avoid double release "accidents".
195+void mir_display_config_release(DisplayConfig const& config) = delete;
196+}
197+}
198+
199+#endif //MIR_CLIENT_DISPLAY_CONFIG_H
200
201=== modified file 'include/mir/client/window.h'
202--- include/mir/client/window.h 2017-02-14 13:50:07 +0000
203+++ include/mir/client/window.h 2017-03-03 10:23:15 +0000
204@@ -55,6 +55,10 @@
205 static void deleter(MirWindow* window) { mir_window_release_sync(window); }
206 std::shared_ptr<MirWindow> self;
207 };
208+
209+// Provide a deleted overload to avoid double release "accidents".
210+void mir_window_release_sync(Window const& window) = delete;
211+void mir_surface_release_sync(Window const& window) = delete;
212 }
213 }
214
215
216=== modified file 'include/mir/client/window_id.h'
217--- include/mir/client/window_id.h 2017-02-15 14:03:55 +0000
218+++ include/mir/client/window_id.h 2017-03-03 10:23:15 +0000
219@@ -22,15 +22,14 @@
220 #include <mir/client/detail/mir_forward_compatibility.h>
221 #if MIR_CLIENT_VERSION < MIR_VERSION_NUMBER(3, 5, 0)
222 #include <mir_toolkit/mir_surface.h>
223+auto const mir_window_request_window_id_sync = mir_surface_request_persistent_id_sync;
224 #else
225 #include <mir_toolkit/mir_window.h>
226+#endif
227+
228+#if MIR_CLIENT_API_VERSION < MIR_VERSION_NUMBER(0, 26, 1)
229+#if MIR_CLIENT_VERSION == MIR_VERSION_NUMBER(3, 5, 0)
230 #include <mir_toolkit/mir_persistent_id.h>
231-#endif
232-
233-#if MIR_CLIENT_API_VERSION < MIR_VERSION_NUMBER(0, 26, 1)
234-#if MIR_CLIENT_VERSION < MIR_VERSION_NUMBER(3, 5, 0)
235-auto const mir_window_request_window_id_sync = mir_surface_request_persistent_id_sync;
236-#else
237 auto const mir_window_request_window_id_sync = mir_window_request_persistent_id_sync;
238 #endif
239 auto const mir_window_id_as_string = mir_persistent_id_as_string;
240
241=== modified file 'include/mir/client/window_spec.h'
242--- include/mir/client/window_spec.h 2017-02-14 13:50:07 +0000
243+++ include/mir/client/window_spec.h 2017-03-03 10:23:15 +0000
244@@ -319,6 +319,10 @@
245 static void deleter(MirWindowSpec* spec) { mir_window_spec_release(spec); }
246 std::shared_ptr<MirWindowSpec> self;
247 };
248+
249+// Provide a deleted overload to avoid double release "accidents".
250+void mir_window_spec_release(WindowSpec const& spec) = delete;
251+void mir_surface_spec_release(WindowSpec const& spec) = delete;
252 }
253 }
254
255
256=== modified file 'include/miral/window.h'
257--- include/miral/window.h 2016-08-05 08:59:05 +0000
258+++ include/miral/window.h 2017-03-03 10:23:15 +0000
259@@ -63,15 +63,20 @@
260 friend bool operator==(Window const& lhs, Window const& rhs);
261 friend bool operator==(std::shared_ptr<mir::scene::Surface> const& lhs, Window const& rhs);
262 friend bool operator==(Window const& lhs, std::shared_ptr<mir::scene::Surface> const& rhs);
263+ friend bool operator<(Window const& lhs, Window const& rhs);
264 };
265
266 bool operator==(Window const& lhs, Window const& rhs);
267 bool operator==(std::shared_ptr<mir::scene::Surface> const& lhs, Window const& rhs);
268 bool operator==(Window const& lhs, std::shared_ptr<mir::scene::Surface> const& rhs);
269+bool operator<(Window const& lhs, Window const& rhs);
270
271 inline bool operator!=(Window const& lhs, Window const& rhs) { return !(lhs == rhs); }
272 inline bool operator!=(std::shared_ptr<mir::scene::Surface> const& lhs, Window const& rhs) { return !(lhs == rhs); }
273 inline bool operator!=(Window const& lhs, std::shared_ptr<mir::scene::Surface> const& rhs) { return !(lhs == rhs); }
274+inline bool operator>(Window const& lhs, Window const& rhs) { return rhs < lhs; }
275+inline bool operator<=(Window const& lhs, Window const& rhs) { return !(lhs > rhs); }
276+inline bool operator>=(Window const& lhs, Window const& rhs) { return !(lhs < rhs); }
277 }
278
279 #endif //MIRAL_WINDOW_H
280
281=== modified file 'include/miral/window_manager_tools.h'
282--- include/miral/window_manager_tools.h 2017-02-10 10:44:12 +0000
283+++ include/miral/window_manager_tools.h 2017-03-03 10:23:15 +0000
284@@ -1,5 +1,5 @@
285 /*
286- * Copyright © 2016 Canonical Ltd.
287+ * Copyright © 2016-2017 Canonical Ltd.
288 *
289 * This program is free software: you can redistribute it and/or modify it
290 * under the terms of the GNU General Public License version 3,
291@@ -39,6 +39,19 @@
292 struct ApplicationInfo;
293 class WindowSpecification;
294
295+/**
296+ * Workspace is intentionally opaque in the miral API. Its only purpose is to
297+ * provide a shared_ptr which is used as an identifier.
298+ *
299+ * The MirAL implementation of workspaces only prescribes the following:
300+ * o When child windows are created they are added to all(any) workspaces of parent
301+ * o Focus changes will first try windows with a common workspace
302+ * o Adding/removing windows to a workspace affects the whole ancestor/decendent tree
303+ *
304+ * The presentation of workspaces is left entirely to the policy
305+ */
306+class Workspace;
307+
308 class WindowManagerToolsImplementation;
309
310 /// Window management functions for querying and updating MirAL's model
311@@ -141,6 +154,9 @@
312 /// make the next surface active within the active application
313 void focus_next_within_application();
314
315+ /// make the prev surface active within the active application
316+ void focus_prev_within_application();
317+
318 /// Find the topmost window at the cursor
319 auto window_at(mir::geometry::Point cursor) const -> Window;
320
321@@ -158,6 +174,56 @@
322
323 /// Set a default size and position to reflect state change
324 void place_and_size_for_state(WindowSpecification& modifications, WindowInfo const& window_info) const;
325+
326+ /** Create a workspace.
327+ * \remark the tools hold only a weak_ptr<> to the workspace - there is no need for an explicit "destroy".
328+ * @return a shared_ptr owning the workspace
329+ */
330+ auto create_workspace() -> std::shared_ptr<Workspace>;
331+
332+ /**
333+ * Add the tree containing window to a workspace
334+ * @param window the window
335+ * @param workspace the workspace;
336+ */
337+ void add_tree_to_workspace(Window const& window, std::shared_ptr<Workspace> const& workspace);
338+
339+ /**
340+ * Remove the tree containing window from a workspace
341+ * @param window the window
342+ * @param workspace the workspace;
343+ */
344+ void remove_tree_from_workspace(Window const& window, std::shared_ptr<Workspace> const& workspace);
345+
346+ /**
347+ * Moves all the content from one workspace to another
348+ * @param from_workspace the workspace to move the windows from;
349+ * @param to_workspace the workspace to move the windows to;
350+ */
351+ void move_workspace_content_to_workspace(
352+ std::shared_ptr<Workspace> const& to_workspace,
353+ std::shared_ptr<Workspace> const& from_workspace);
354+
355+ /**
356+ * invoke callback with each workspace containing window
357+ * \warning it is unsafe to add or remove windows from workspaces from the callback during enumeration
358+ * @param window
359+ * @param callback
360+ */
361+ void for_each_workspace_containing(
362+ Window const& window,
363+ std::function<void(std::shared_ptr<Workspace> const& workspace)> const& callback);
364+
365+ /**
366+ * invoke callback with each window contained in workspace
367+ * \warning it is unsafe to add or remove windows from workspaces from the callback during enumeration
368+ * @param workspace
369+ * @param callback
370+ */
371+ void for_each_window_in_workspace(
372+ std::shared_ptr<Workspace> const& workspace,
373+ std::function<void(Window const& window)> const& callback);
374+
375 /** @} */
376
377 /** Multi-thread support
378
379=== added file 'include/miral/workspace_policy.h'
380--- include/miral/workspace_policy.h 1970-01-01 00:00:00 +0000
381+++ include/miral/workspace_policy.h 2017-03-03 10:23:15 +0000
382@@ -0,0 +1,88 @@
383+/*
384+ * Copyright © 2017 Canonical Ltd.
385+ *
386+ * This program is free software: you can redistribute it and/or modify it
387+ * under the terms of the GNU General Public License version 3,
388+ * as published by the Free Software Foundation.
389+ *
390+ * This program is distributed in the hope that it will be useful,
391+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
392+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
393+ * GNU General Public License for more details.
394+ *
395+ * You should have received a copy of the GNU General Public License
396+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
397+ *
398+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
399+ */
400+
401+#ifndef MIRAL_WORKSPACE_POLICY_H
402+#define MIRAL_WORKSPACE_POLICY_H
403+
404+#include "miral/version.h"
405+
406+#include <memory>
407+#include <vector>
408+
409+namespace miral
410+{
411+class Window;
412+
413+/**
414+ * Workspace is intentionally opaque in the miral API. Its only purpose is to
415+ * provide a shared_ptr which is used as an identifier.
416+ */
417+class Workspace;
418+
419+/**
420+ * Advise changes to workspaces.
421+ *
422+ * \note This interface is intended to be implemented by a WindowManagementPolicy
423+ * implementation, we can't add these functions directly to that interface without
424+ * breaking ABI (the vtab could be incompatible).
425+ * When initializing the window manager this interface will be detected by
426+ * dynamic_cast and registered accordingly.
427+ */
428+class WorkspacePolicy
429+{
430+public:
431+/** @name notification of WM events that the policy may need to track.
432+ * These calls happen "under lock" and are wrapped by the usual
433+ * WindowManagementPolicy::advise_begin(), advise_end() calls.
434+ * They should not call WindowManagerTools::invoke_under_lock()
435+ * @{ */
436+
437+ /** Notification that windows are being added to a workspace.
438+ * These windows are ordered with parents before children,
439+ * and form a single tree rooted at the first element.
440+ *
441+ * @param workspace the workspace
442+ * @param windows the windows
443+ */
444+ virtual void advise_adding_to_workspace(
445+ std::shared_ptr<Workspace> const& workspace,
446+ std::vector<Window> const& windows);
447+
448+ /** Notification that windows are being removed from a workspace.
449+ * These windows are ordered with parents before children,
450+ * and form a single tree rooted at the first element.
451+ *
452+ * @param workspace the workspace
453+ * @param windows the windows
454+ */
455+ virtual void advise_removing_from_workspace(
456+ std::shared_ptr<Workspace> const& workspace,
457+ std::vector<Window> const& windows);
458+
459+/** @} */
460+
461+ virtual ~WorkspacePolicy() = default;
462+ WorkspacePolicy() = default;
463+ WorkspacePolicy(WorkspacePolicy const&) = delete;
464+ WorkspacePolicy& operator=(WorkspacePolicy const&) = delete;
465+};
466+#if MIRAL_VERSION >= MIR_VERSION_NUMBER(2, 0, 0)
467+#error "We've presumably broken ABI - please roll this interface into WindowManagementPolicy"
468+#endif
469+}
470+#endif //MIRAL_WORKSPACE_POLICY_H
471
472=== modified file 'miral-kiosk/kiosk_window_manager.cpp'
473--- miral-kiosk/kiosk_window_manager.cpp 2017-01-26 11:30:51 +0000
474+++ miral-kiosk/kiosk_window_manager.cpp 2017-03-03 10:23:15 +0000
475@@ -58,6 +58,14 @@
476
477 return true;
478 }
479+ else if (action == mir_keyboard_action_down &&
480+ modifiers == (mir_input_event_modifier_alt | mir_input_event_modifier_shift) &&
481+ scan_code == KEY_GRAVE)
482+ {
483+ tools.focus_prev_within_application();
484+
485+ return true;
486+ }
487 else if (action == mir_keyboard_action_down && scan_code == KEY_F4)
488 {
489 switch (modifiers & modifier_mask)
490
491=== modified file 'miral-kiosk/sw_splash.cpp'
492--- miral-kiosk/sw_splash.cpp 2017-02-14 11:49:59 +0000
493+++ miral-kiosk/sw_splash.cpp 2017-03-03 10:23:15 +0000
494@@ -20,7 +20,6 @@
495
496 #include <mir/client/window.h>
497
498-#include <mir_toolkit/mir_connection.h>
499 #include <mir_toolkit/mir_buffer_stream.h>
500
501 #include <chrono>
502@@ -61,14 +60,13 @@
503 return *pixel_formats;
504 }
505
506-auto create_window(MirConnection* connection, MirPixelFormat pixel_format) -> MirWindow*
507+auto create_window(MirConnection* connection, MirPixelFormat pixel_format) -> mir::client::Window
508 {
509- auto const spec = mir::client::WindowSpec::for_normal_window(connection, 42, 42, pixel_format)
510+ return mir::client::WindowSpec::for_normal_window(connection, 42, 42, pixel_format)
511 .set_name("splash")
512 .set_buffer_usage(mir_buffer_usage_software)
513- .set_fullscreen_on_output(0);
514-
515- return mir_create_window_sync(spec);
516+ .set_fullscreen_on_output(0)
517+ .create_window();
518 }
519
520 void render_pattern(MirGraphicsRegion *region, uint8_t pattern[])
521@@ -150,6 +148,4 @@
522 std::this_thread::sleep_for(std::chrono::milliseconds(200));
523 }
524 while (std::chrono::steady_clock::now() < time_limit);
525-
526- mir_window_release_sync(surface);
527 }
528
529=== modified file 'miral-shell/CMakeLists.txt'
530--- miral-shell/CMakeLists.txt 2016-12-14 11:22:57 +0000
531+++ miral-shell/CMakeLists.txt 2017-03-03 10:23:15 +0000
532@@ -45,7 +45,7 @@
533 shell_main.cpp
534 tiling_window_manager.cpp tiling_window_manager.h
535 titlebar_window_manager.cpp titlebar_window_manager.h
536- titlebar_provider.cpp titlebar_provider.h
537+ decoration_provider.cpp decoration_provider.h
538 titlebar_config.cpp titlebar_config.h
539 )
540
541
542=== renamed file 'miral-shell/titlebar_provider.cpp' => 'miral-shell/decoration_provider.cpp'
543--- miral-shell/titlebar_provider.cpp 2017-02-14 11:49:59 +0000
544+++ miral-shell/decoration_provider.cpp 2017-03-03 10:23:15 +0000
545@@ -1,5 +1,5 @@
546 /*
547- * Copyright © 2016 Canonical Ltd.
548+ * Copyright © 2016-2017 Canonical Ltd.
549 *
550 * This program is free software: you can redistribute it and/or modify it
551 * under the terms of the GNU General Public License version 3,
552@@ -16,9 +16,10 @@
553 * Authored by: Alan Griffiths <alan@octopull.co.uk>
554 */
555
556-#include "titlebar_provider.h"
557+#include "decoration_provider.h"
558 #include "titlebar_config.h"
559
560+#include <mir/client/display_config.h>
561 #include <mir/client/window_spec.h>
562
563 #include <mir_toolkit/mir_buffer_stream.h>
564@@ -37,8 +38,9 @@
565 namespace
566 {
567 int const title_bar_height = 12;
568+char const* const wallpaper_name = "wallpaper";
569
570-void null_surface_callback(MirWindow*, void*) {}
571+void null_window_callback(MirWindow*, void*) {}
572
573 struct Printer
574 {
575@@ -48,6 +50,7 @@
576 Printer& operator=(Printer const&) = delete;
577
578 void print(MirGraphicsRegion const& region, std::string const& title, int const intensity);
579+ void printhelp(MirGraphicsRegion const& region);
580
581 private:
582 std::wstring_convert<std::codecvt_utf16<wchar_t>> converter;
583@@ -59,11 +62,7 @@
584
585 void paint_surface(MirWindow* surface, std::string const& title, int const intensity)
586 {
587-#if MIR_CLIENT_VERSION <= MIR_VERSION_NUMBER(3, 4, 0)
588- MirBufferStream* buffer_stream = mir_surface_get_buffer_stream(surface);
589-#else
590 MirBufferStream* buffer_stream = mir_window_get_buffer_stream(surface);
591-#endif
592
593 // TODO sometimes buffer_stream is nullptr - find out why (and fix).
594 // (Only observed when creating a lot of clients at once)
595@@ -152,22 +151,116 @@
596 base_y += glyph->advance.y >> 6;
597 }
598 }
599+
600+void Printer::printhelp(MirGraphicsRegion const& region)
601+{
602+ static char const* const helptext[] =
603+ {
604+ "Welcome to miral-shell",
605+ "",
606+ "Keyboard shortcuts:",
607+ "",
608+ " o Switch apps: Alt-Tab, tap or click on the corresponding window",
609+ " o Next (previous) app window: Alt-` (Alt-Shift-`)",
610+ "",
611+ " o Move window: Alt-leftmousebutton drag (three finger drag)",
612+ " o Resize window: Alt-middle_button drag (three finger pinch)",
613+ "",
614+ " o Maximize/restore current window (to display size). : Alt-F11",
615+ " o Maximize/restore current window (to display height): Shift-F11",
616+ " o Maximize/restore current window (to display width) : Ctrl-F11",
617+ "",
618+ " o Switch workspace: Meta-Alt-[F1|F2|F3|F4]",
619+ " o Switch workspace taking active window: Meta-Ctrl-[F1|F2|F3|F4]",
620+ "",
621+ " o To exit: Ctrl-Alt-BkSp",
622+ };
623+
624+ int help_width = 0;
625+ unsigned int help_height = 0;
626+ unsigned int line_height = 0;
627+
628+ for (auto const* rawline : helptext)
629+ {
630+ int line_width = 0;
631+
632+ auto const line = converter.from_bytes(rawline);
633+
634+ auto const fwidth = std::min(region.width / 60, 20);
635+
636+ FT_Set_Pixel_Sizes(face, fwidth, 0);
637+
638+ for (auto const& ch : line)
639+ {
640+ FT_Load_Glyph(face, FT_Get_Char_Index(face, ch), FT_LOAD_DEFAULT);
641+ auto const glyph = face->glyph;
642+ FT_Render_Glyph(glyph, FT_RENDER_MODE_NORMAL);
643+
644+ line_width += glyph->advance.x >> 6;
645+ line_height = std::max(line_height, glyph->bitmap.rows + glyph->bitmap.rows/2);
646+ }
647+
648+ if (help_width < line_width) help_width = line_width;
649+ help_height += line_height;
650+ }
651+
652+ int base_y = (region.height - help_height)/2;
653+
654+ for (auto const* rawline : helptext)
655+ {
656+ int base_x = (region.width - help_width)/2;
657+
658+ auto const line = converter.from_bytes(rawline);
659+
660+ for (auto const& ch : line)
661+ {
662+ FT_Load_Glyph(face, FT_Get_Char_Index(face, ch), FT_LOAD_DEFAULT);
663+ auto const glyph = face->glyph;
664+ FT_Render_Glyph(glyph, FT_RENDER_MODE_NORMAL);
665+
666+ auto const& bitmap = glyph->bitmap;
667+ auto const x = base_x + glyph->bitmap_left;
668+
669+ if (static_cast<int>(x + bitmap.width) <= region.width)
670+ {
671+ unsigned char* src = bitmap.buffer;
672+
673+ auto const y = base_y - glyph->bitmap_top;
674+ char* dest = region.vaddr + y * region.stride + 4 * x;
675+
676+ for (auto row = 0u; row != bitmap.rows; ++row)
677+ {
678+ for (auto col = 0u; col != 4 * bitmap.width; ++col)
679+ dest[col] |= src[col / 4]/2;
680+
681+ src += bitmap.pitch;
682+ dest += region.stride;
683+
684+ if (dest > region.vaddr + region.height * region.stride)
685+ break;
686+ }
687+ }
688+
689+ base_x += glyph->advance.x >> 6;
690+ }
691+ base_y += line_height;
692+ }
693+}
694 }
695
696 using namespace mir::client;
697 using namespace mir::geometry;
698
699-TitlebarProvider::TitlebarProvider(miral::WindowManagerTools const& tools) : tools{tools}
700-{
701-
702-}
703-
704-TitlebarProvider::~TitlebarProvider()
705-{
706- stop();
707-}
708-
709-void TitlebarProvider::stop()
710+DecorationProvider::DecorationProvider(miral::WindowManagerTools const& tools) : tools{tools}
711+{
712+
713+}
714+
715+DecorationProvider::~DecorationProvider()
716+{
717+}
718+
719+void DecorationProvider::stop()
720 {
721 enqueue_work([this]
722 {
723@@ -177,30 +270,83 @@
724
725 enqueue_work([this]
726 {
727+ if (connection)
728+ {
729+#if MIR_CLIENT_API_VERSION < MIR_VERSION_NUMBER(0, 26, 2)
730+ auto const Workaround_lp_1667645 =
731+ WindowSpec::for_normal_window(connection, 100, 100, mir_pixel_format_xrgb_8888)
732+ .set_name(wallpaper_name).create_window();
733+#endif
734+ wallpaper.erase(begin(wallpaper), end(wallpaper));
735+ }
736 connection.reset();
737- stop_work();
738 });
739-}
740-
741-void TitlebarProvider::operator()(mir::client::Connection connection)
742+ stop_work();
743+}
744+
745+namespace
746+{
747+void render_pattern(MirGraphicsRegion* region, uint8_t const pattern[])
748+{
749+ char* row = region->vaddr;
750+
751+ for (int j = 0; j < region->height; j++)
752+ {
753+ uint32_t* pixel = (uint32_t*)row;
754+
755+ for (int i = 0; i < region->width; i++)
756+ memcpy(pixel + i, pattern, sizeof pixel[i]);
757+
758+ row += region->stride;
759+ }
760+
761+ static Printer printer;
762+ printer.printhelp(*region);
763+}
764+}
765+
766+void DecorationProvider::operator()(Connection connection)
767 {
768 this->connection = connection;
769+
770+ DisplayConfig const display_conf{this->connection};
771+
772+ display_conf.for_each_output([this](MirOutput const* output)
773+ {
774+ wallpaper.push_back(
775+ WindowSpec::for_gloss(this->connection, 100, 100)
776+ .set_pixel_format(mir_pixel_format_xrgb_8888)
777+ .set_buffer_usage(mir_buffer_usage_software)
778+ .set_fullscreen_on_output(mir_output_get_id(output))
779+ .set_name(wallpaper_name).create_window());
780+
781+ MirGraphicsRegion graphics_region;
782+ MirBufferStream* buffer_stream = mir_window_get_buffer_stream(wallpaper.back());
783+
784+ mir_buffer_stream_get_graphics_region(buffer_stream, &graphics_region);
785+
786+ static uint8_t const pattern[4] = { 0x00, 0x00, 0x00, 0x00 };
787+
788+ render_pattern(&graphics_region, pattern);
789+ mir_buffer_stream_swap_buffers_sync(buffer_stream);
790+ });
791+
792 start_work();
793 }
794
795-void TitlebarProvider::operator()(std::weak_ptr<mir::scene::Session> const& session)
796+void DecorationProvider::operator()(std::weak_ptr<mir::scene::Session> const& session)
797 {
798 std::lock_guard<decltype(mutex)> lock{mutex};
799 this->weak_session = session;
800 }
801
802-auto TitlebarProvider::session() const -> std::shared_ptr<mir::scene::Session>
803+auto DecorationProvider::session() const -> std::shared_ptr<mir::scene::Session>
804 {
805 std::lock_guard<decltype(mutex)> lock{mutex};
806 return weak_session.lock();
807 }
808
809-void TitlebarProvider::create_titlebar_for(miral::Window const& window)
810+void DecorationProvider::create_titlebar_for(miral::Window const& window)
811 {
812 enqueue_work([this, window]
813 {
814@@ -220,12 +366,12 @@
815 });
816 }
817
818-void TitlebarProvider::paint_titlebar_for(miral::WindowInfo const& info, int intensity)
819+void DecorationProvider::paint_titlebar_for(miral::WindowInfo const& info, int intensity)
820 {
821- this->intensity = intensity;
822-
823 if (auto data = find_titlebar_data(info.window()))
824 {
825+ data->intensity = intensity;
826+
827 auto const title = info.name();
828
829 if (auto surface = data->titlebar.load())
830@@ -240,7 +386,7 @@
831 }
832 }
833
834-void TitlebarProvider::destroy_titlebar_for(miral::Window const& window)
835+void DecorationProvider::destroy_titlebar_for(miral::Window const& window)
836 {
837 if (auto data = find_titlebar_data(window))
838 {
839@@ -248,11 +394,7 @@
840 {
841 enqueue_work([surface]
842 {
843-#if MIR_CLIENT_VERSION <= MIR_VERSION_NUMBER(3, 4, 0)
844- mir_surface_release(surface, &null_surface_callback, nullptr);
845-#else
846- mir_window_release(surface, &null_surface_callback, nullptr);
847-#endif
848+ mir_window_release(surface, &null_window_callback, nullptr);
849 });
850 }
851
852@@ -264,7 +406,7 @@
853 }
854 }
855
856-void TitlebarProvider::resize_titlebar_for(miral::WindowInfo const& window_info, Size const& size)
857+void DecorationProvider::resize_titlebar_for(miral::WindowInfo const& window_info, Size const& size)
858 {
859 auto const window = window_info.window();
860
861@@ -279,9 +421,10 @@
862 }
863 }
864
865-void TitlebarProvider::place_new_titlebar(miral::WindowSpecification& window_spec)
866+void DecorationProvider::place_new_decoration(miral::WindowSpecification& window_spec)
867 {
868 auto const name = window_spec.name().value();
869+ if (name == wallpaper_name) return;
870
871 std::lock_guard<decltype(mutex)> lock{mutex};
872
873@@ -296,8 +439,10 @@
874 window_spec.top_left() = parent_window.top_left() - Displacement{0, title_bar_height};
875 }
876
877-void TitlebarProvider::advise_new_titlebar(miral::WindowInfo const& window_info)
878+void DecorationProvider::advise_new_titlebar(miral::WindowInfo const& window_info)
879 {
880+ if (window_info.name() == wallpaper_name) return;
881+
882 {
883 std::lock_guard<decltype(mutex)> lock{mutex};
884
885@@ -307,7 +452,7 @@
886 tools.raise_tree(window_info.parent());
887 }
888
889-void TitlebarProvider::advise_state_change(miral::WindowInfo const& window_info, MirWindowState state)
890+void DecorationProvider::advise_state_change(miral::WindowInfo const& window_info, MirWindowState state)
891 {
892 if (auto titlebar = find_titlebar_window(window_info.window()))
893 {
894@@ -332,34 +477,33 @@
895 }
896 }
897
898-void TitlebarProvider::repaint_titlebar_for(miral::WindowInfo const& window_info)
899+void DecorationProvider::repaint_titlebar_for(miral::WindowInfo const& window_info)
900 {
901 if (auto data = find_titlebar_data(window_info.window()))
902 {
903 auto const title = window_info.name();
904
905 if (auto surface = data->titlebar.load())
906- enqueue_work([this, surface, title]{ paint_surface(surface, title, intensity); });
907+ {
908+ enqueue_work([this, surface, title, intensity=data->intensity.load()]
909+ { paint_surface(surface, title, intensity); });
910+ }
911 }
912 }
913
914-TitlebarProvider::Data::~Data()
915+DecorationProvider::Data::~Data()
916 {
917 if (auto surface = titlebar.exchange(nullptr))
918-#if MIR_CLIENT_VERSION <= MIR_VERSION_NUMBER(3, 4, 0)
919- mir_surface_release(surface, &null_surface_callback, nullptr);
920-#else
921- mir_window_release(surface, &null_surface_callback, nullptr);
922-#endif
923+ mir_window_release(surface, &null_window_callback, nullptr);
924 }
925
926-void TitlebarProvider::insert(MirWindow* surface, Data* data)
927+void DecorationProvider::insert(MirWindow* surface, Data* data)
928 {
929 data->on_create(surface);
930 data->titlebar = surface;
931 }
932
933-TitlebarProvider::Data* TitlebarProvider::find_titlebar_data(miral::Window const& window)
934+DecorationProvider::Data* DecorationProvider::find_titlebar_data(miral::Window const& window)
935 {
936 std::lock_guard<decltype(mutex)> lock{mutex};
937
938@@ -368,7 +512,7 @@
939 return (find != window_to_titlebar.end()) ? &find->second : nullptr;
940 }
941
942-miral::Window TitlebarProvider::find_titlebar_window(miral::Window const& window) const
943+miral::Window DecorationProvider::find_titlebar_window(miral::Window const& window) const
944 {
945 std::lock_guard<decltype(mutex)> lock{mutex};
946
947@@ -377,9 +521,18 @@
948 return (find != window_to_titlebar.end()) ? find->second.window : miral::Window{};
949 }
950
951+bool DecorationProvider::is_decoration(miral::Window const& window) const
952+{
953+ return window.application() == session();
954+}
955+
956+bool DecorationProvider::is_titlebar(miral::WindowInfo const& window_info) const
957+{
958+ return window_info.window().application() == session() && window_info.name() != wallpaper_name;
959+}
960+
961 Worker::~Worker()
962 {
963- if (worker.joinable()) worker.join();
964 }
965
966 void Worker::do_work()
967@@ -407,7 +560,7 @@
968
969 void Worker::start_work()
970 {
971- worker = std::thread{[this] { do_work(); }};
972+ do_work();
973 }
974
975 void Worker::stop_work()
976
977=== renamed file 'miral-shell/titlebar_provider.h' => 'miral-shell/decoration_provider.h'
978--- miral-shell/titlebar_provider.h 2017-02-14 11:49:59 +0000
979+++ miral-shell/decoration_provider.h 2017-03-03 10:23:15 +0000
980@@ -1,5 +1,5 @@
981 /*
982- * Copyright © 2016 Canonical Ltd.
983+ * Copyright © 2016-2017 Canonical Ltd.
984 *
985 * This program is free software: you can redistribute it and/or modify it
986 * under the terms of the GNU General Public License version 3,
987@@ -16,20 +16,21 @@
988 * Authored by: Alan Griffiths <alan@octopull.co.uk>
989 */
990
991-#ifndef MIRAL_SHELL_TITLEBAR_PROVIDER_H
992-#define MIRAL_SHELL_TITLEBAR_PROVIDER_H
993+#ifndef MIRAL_SHELL_DECORATION_PROVIDER_H
994+#define MIRAL_SHELL_DECORATION_PROVIDER_H
995
996
997 #include <miral/window_manager_tools.h>
998
999+#include <mir/client/connection.h>
1000+#include <mir/client/window.h>
1001+
1002 #include <mir/geometry/rectangle.h>
1003 #include <mir_toolkit/client_types.h>
1004
1005 #include <atomic>
1006 #include <map>
1007 #include <mutex>
1008-#include <mir/client/connection.h>
1009-#include <thread>
1010 #include <condition_variable>
1011 #include <queue>
1012
1013@@ -49,16 +50,15 @@
1014 std::condition_variable work_cv;
1015 WorkQueue work_queue;
1016 bool work_done = false;
1017- std::thread worker;
1018
1019 void do_work();
1020 };
1021
1022-class TitlebarProvider : Worker
1023+class DecorationProvider : Worker
1024 {
1025 public:
1026- TitlebarProvider(miral::WindowManagerTools const& tools);
1027- ~TitlebarProvider();
1028+ DecorationProvider(miral::WindowManagerTools const& tools);
1029+ ~DecorationProvider();
1030
1031 void operator()(mir::client::Connection connection);
1032 void operator()(std::weak_ptr<mir::scene::Session> const& session);
1033@@ -66,7 +66,7 @@
1034 auto session() const -> std::shared_ptr<mir::scene::Session>;
1035
1036 void create_titlebar_for(miral::Window const& window);
1037- void place_new_titlebar(miral::WindowSpecification& window_spec);
1038+ void place_new_decoration(miral::WindowSpecification& window_spec);
1039 void paint_titlebar_for(miral::WindowInfo const& window, int intensity);
1040 void destroy_titlebar_for(miral::Window const& window);
1041 void resize_titlebar_for(miral::WindowInfo const& window_info, mir::geometry::Size const& size);
1042@@ -75,10 +75,14 @@
1043
1044 void stop();
1045
1046+ bool is_decoration(miral::Window const& window) const;
1047+ bool is_titlebar(miral::WindowInfo const& window_info) const;
1048+
1049 private:
1050 struct Data
1051 {
1052 std::atomic<MirWindow*> titlebar{nullptr};
1053+ std::atomic<int> intensity{0xff};
1054 std::function<void(MirWindow* surface)> on_create{[](MirWindow*){}};
1055 miral::Window window;
1056
1057@@ -91,8 +95,8 @@
1058 miral::WindowManagerTools tools;
1059 std::mutex mutable mutex;
1060 mir::client::Connection connection;
1061+ std::vector<mir::client::Window> wallpaper;
1062 std::weak_ptr<mir::scene::Session> weak_session;
1063- std::atomic<int> intensity{0xff};
1064
1065 SurfaceMap window_to_titlebar;
1066 TitleMap windows_awaiting_titlebar;
1067@@ -104,4 +108,4 @@
1068 };
1069
1070
1071-#endif //MIRAL_SHELL_TITLEBAR_PROVIDER_H
1072+#endif //MIRAL_SHELL_DECORATION_PROVIDER_H
1073
1074=== modified file 'miral-shell/shell_main.cpp'
1075--- miral-shell/shell_main.cpp 2016-12-01 11:48:56 +0000
1076+++ miral-shell/shell_main.cpp 2017-03-03 10:23:15 +0000
1077@@ -1,5 +1,5 @@
1078 /*
1079- * Copyright © 2016 Canonical Ltd.
1080+ * Copyright © 2016-2017 Canonical Ltd.
1081 *
1082 * This program is free software: you can redistribute it and/or modify
1083 * it under the terms of the GNU General Public License version 3 as
1084@@ -37,17 +37,21 @@
1085 {
1086 using namespace miral;
1087
1088+ std::function<void()> shutdown_hook{[]{}};
1089+
1090 SpinnerSplash spinner;
1091 InternalClientLauncher launcher;
1092 ActiveOutputsMonitor outputs_monitor;
1093 WindowManagerOptions window_managers
1094 {
1095- add_window_manager_policy<TitlebarWindowManagerPolicy>("titlebar", spinner, launcher),
1096+ add_window_manager_policy<TitlebarWindowManagerPolicy>("titlebar", spinner, launcher, shutdown_hook),
1097 add_window_manager_policy<TilingWindowManagerPolicy>("tiling", spinner, launcher, outputs_monitor),
1098 };
1099
1100 MirRunner runner{argc, argv};
1101
1102+ runner.add_stop_callback([&] { shutdown_hook(); });
1103+
1104 auto const quit_on_ctrl_alt_bksp = [&](MirEvent const* event)
1105 {
1106 if (mir_event_get_type(event) != mir_event_type_input)
1107
1108=== modified file 'miral-shell/tiling_window_manager.cpp'
1109--- miral-shell/tiling_window_manager.cpp 2017-01-26 11:30:51 +0000
1110+++ miral-shell/tiling_window_manager.cpp 2017-03-03 10:23:15 +0000
1111@@ -333,6 +333,14 @@
1112
1113 return true;
1114 }
1115+ else if (action == mir_keyboard_action_down &&
1116+ modifiers == (mir_input_event_modifier_alt | mir_input_event_modifier_shift) &&
1117+ scan_code == KEY_GRAVE)
1118+ {
1119+ tools.focus_prev_within_application();
1120+
1121+ return true;
1122+ }
1123
1124 return false;
1125 }
1126
1127=== modified file 'miral-shell/titlebar_window_manager.cpp'
1128--- miral-shell/titlebar_window_manager.cpp 2017-01-26 11:30:51 +0000
1129+++ miral-shell/titlebar_window_manager.cpp 2017-03-03 10:23:15 +0000
1130@@ -1,5 +1,5 @@
1131 /*
1132- * Copyright © 2016 Canonical Ltd.
1133+ * Copyright © 2016-2017 Canonical Ltd.
1134 *
1135 * This program is free software: you can redistribute it and/or modify it
1136 * under the terms of the GNU General Public License version 3,
1137@@ -17,7 +17,7 @@
1138 */
1139
1140 #include "titlebar_window_manager.h"
1141-#include "titlebar_provider.h"
1142+#include "decoration_provider.h"
1143
1144 #include <miral/application_info.h>
1145 #include <miral/internal_client.h>
1146@@ -32,17 +32,36 @@
1147 namespace
1148 {
1149 int const title_bar_height = 12;
1150+
1151+struct PolicyData
1152+{
1153+ bool in_hidden_workspace{false};
1154+
1155+ MirWindowState old_state;
1156+};
1157+
1158+inline PolicyData& policy_data_for(WindowInfo const& info)
1159+{
1160+ return *std::static_pointer_cast<PolicyData>(info.userdata());
1161+}
1162 }
1163
1164 TitlebarWindowManagerPolicy::TitlebarWindowManagerPolicy(
1165 WindowManagerTools const& tools,
1166 SpinnerSplash const& spinner,
1167- miral::InternalClientLauncher const& launcher) :
1168+ miral::InternalClientLauncher const& launcher,
1169+ std::function<void()>& shutdown_hook) :
1170 CanonicalWindowManagerPolicy(tools),
1171 spinner{spinner},
1172- titlebar_provider{std::make_unique<TitlebarProvider>(tools)}
1173+ decoration_provider{std::make_unique<DecorationProvider>(tools)}
1174 {
1175- launcher.launch("decorations", *titlebar_provider);
1176+ launcher.launch("decorations", *decoration_provider);
1177+ shutdown_hook = [this] { decoration_provider->stop(); };
1178+
1179+ for (auto key : {KEY_F1, KEY_F2, KEY_F3, KEY_F4})
1180+ key_to_workspace[key] = this->tools.create_workspace();
1181+
1182+ active_workspace = key_to_workspace[KEY_F1];
1183 }
1184
1185 TitlebarWindowManagerPolicy::~TitlebarWindowManagerPolicy() = default;
1186@@ -99,9 +118,9 @@
1187 {
1188 if (auto const possible_titlebar = tools.window_at(old_cursor))
1189 {
1190- if (possible_titlebar.application() == titlebar_provider->session())
1191+ auto const& info = tools.info_for(possible_titlebar);
1192+ if (decoration_provider->is_titlebar(info))
1193 {
1194- auto const& info = tools.info_for(possible_titlebar);
1195 if (tools.select_active_window(info.parent()) == info.parent())
1196 tools.drag_active_window(cursor - old_cursor);
1197 consumes_event = true;
1198@@ -266,25 +285,31 @@
1199 {
1200 CanonicalWindowManagerPolicy::advise_new_window(window_info);
1201
1202- auto const application = window_info.window().application();
1203+ auto const parent = window_info.parent();
1204
1205- if (application == titlebar_provider->session())
1206+ if (decoration_provider->is_titlebar(window_info))
1207 {
1208- titlebar_provider->advise_new_titlebar(window_info);
1209-
1210- auto const parent = window_info.parent();
1211+ decoration_provider->advise_new_titlebar(window_info);
1212
1213 if (tools.active_window() == parent)
1214- titlebar_provider->paint_titlebar_for(tools.info_for(parent), 0xFF);
1215+ decoration_provider->paint_titlebar_for(tools.info_for(parent), 0xFF);
1216 else
1217- titlebar_provider->paint_titlebar_for(tools.info_for(parent), 0x3F);
1218+ decoration_provider->paint_titlebar_for(tools.info_for(parent), 0x3F);
1219+ }
1220+
1221+ if (!parent)
1222+ tools.add_tree_to_workspace(window_info.window(), active_workspace);
1223+ else
1224+ {
1225+ if (policy_data_for(tools.info_for(parent)).in_hidden_workspace)
1226+ apply_workspace_hidden_to(window_info.window());
1227 }
1228 }
1229
1230 void TitlebarWindowManagerPolicy::handle_window_ready(WindowInfo& window_info)
1231 {
1232 if (window_info.window().application() != spinner.session() && window_info.needs_titlebar(window_info.type()))
1233- titlebar_provider->create_titlebar_for(window_info.window());
1234+ decoration_provider->create_titlebar_for(window_info.window());
1235
1236 CanonicalWindowManagerPolicy::handle_window_ready(window_info);
1237 }
1238@@ -293,14 +318,14 @@
1239 {
1240 CanonicalWindowManagerPolicy::advise_focus_lost(info);
1241
1242- titlebar_provider->paint_titlebar_for(info, 0x3F);
1243+ decoration_provider->paint_titlebar_for(info, 0x3F);
1244 }
1245
1246 void TitlebarWindowManagerPolicy::advise_focus_gained(WindowInfo const& info)
1247 {
1248 CanonicalWindowManagerPolicy::advise_focus_gained(info);
1249
1250- titlebar_provider->paint_titlebar_for(info, 0xFF);
1251+ decoration_provider->paint_titlebar_for(info, 0xFF);
1252
1253 // Frig to force the spinner to the top
1254 if (auto const spinner_session = spinner.session())
1255@@ -316,21 +341,21 @@
1256 {
1257 CanonicalWindowManagerPolicy::advise_state_change(window_info, state);
1258
1259- titlebar_provider->advise_state_change(window_info, state);
1260+ decoration_provider->advise_state_change(window_info, state);
1261 }
1262
1263 void TitlebarWindowManagerPolicy::advise_resize(WindowInfo const& window_info, Size const& new_size)
1264 {
1265 CanonicalWindowManagerPolicy::advise_resize(window_info, new_size);
1266
1267- titlebar_provider->resize_titlebar_for(window_info, new_size);
1268+ decoration_provider->resize_titlebar_for(window_info, new_size);
1269 }
1270
1271 void TitlebarWindowManagerPolicy::advise_delete_window(WindowInfo const& window_info)
1272 {
1273 CanonicalWindowManagerPolicy::advise_delete_window(window_info);
1274
1275- titlebar_provider->destroy_titlebar_for(window_info.window());
1276+ decoration_provider->destroy_titlebar_for(window_info.window());
1277 }
1278
1279 bool TitlebarWindowManagerPolicy::handle_keyboard_event(MirKeyboardEvent const* event)
1280@@ -339,6 +364,36 @@
1281 auto const scan_code = mir_keyboard_event_scan_code(event);
1282 auto const modifiers = mir_keyboard_event_modifiers(event) & modifier_mask;
1283
1284+ // Switch workspaces
1285+ if (action == mir_keyboard_action_down &&
1286+ modifiers == (mir_input_event_modifier_alt | mir_input_event_modifier_meta))
1287+ {
1288+ switch (scan_code)
1289+ {
1290+ case KEY_F1:
1291+ case KEY_F2:
1292+ case KEY_F3:
1293+ case KEY_F4:
1294+ switch_workspace_to(key_to_workspace[scan_code]);
1295+ return true;
1296+ }
1297+ }
1298+
1299+ // Switch workspace taking the active window
1300+ if (action == mir_keyboard_action_down &&
1301+ modifiers == (mir_input_event_modifier_ctrl | mir_input_event_modifier_meta))
1302+ {
1303+ switch (scan_code)
1304+ {
1305+ case KEY_F1:
1306+ case KEY_F2:
1307+ case KEY_F3:
1308+ case KEY_F4:
1309+ switch_workspace_to(key_to_workspace[scan_code], tools.active_window());
1310+ return true;
1311+ }
1312+ }
1313+
1314 if (action != mir_keyboard_action_repeat)
1315 end_resize();
1316
1317@@ -364,7 +419,7 @@
1318 }
1319 else if (action == mir_keyboard_action_down && scan_code == KEY_F4)
1320 {
1321- switch (modifiers & modifier_mask)
1322+ switch (modifiers)
1323 {
1324 case mir_input_event_modifier_alt|mir_input_event_modifier_shift:
1325 if (auto const& window = tools.active_window())
1326@@ -396,6 +451,14 @@
1327 return true;
1328 }
1329 else if (action == mir_keyboard_action_down &&
1330+ modifiers == (mir_input_event_modifier_alt | mir_input_event_modifier_shift) &&
1331+ scan_code == KEY_GRAVE)
1332+ {
1333+ tools.focus_prev_within_application();
1334+
1335+ return true;
1336+ }
1337+ else if (action == mir_keyboard_action_down &&
1338 modifiers == (mir_input_event_modifier_ctrl|mir_input_event_modifier_meta))
1339 {
1340 if (auto active_window = tools.active_window())
1341@@ -444,14 +507,6 @@
1342 }
1343 }
1344
1345- // TODO this is a workaround for the lack of a way to detect server exit (Mir bug lp:1593655)
1346- // We need to exit the titlebar_provider "client" thread before the server exits
1347- if (action == mir_keyboard_action_down && scan_code == KEY_BACKSPACE &&
1348- (modifiers == (mir_input_event_modifier_alt | mir_input_event_modifier_ctrl)))
1349- {
1350- titlebar_provider->stop();
1351- }
1352-
1353 return false;
1354 }
1355
1356@@ -568,8 +623,134 @@
1357 if (parameters.state().value() != mir_window_state_fullscreen && needs_titlebar)
1358 parameters.top_left() = Point{parameters.top_left().value().x, parameters.top_left().value().y + DeltaY{title_bar_height}};
1359
1360- if (app_info.application() == titlebar_provider->session())
1361- titlebar_provider->place_new_titlebar(parameters);
1362+ if (app_info.application() == decoration_provider->session())
1363+ decoration_provider->place_new_decoration(parameters);
1364
1365+ parameters.userdata() = std::make_shared<PolicyData>();
1366 return parameters;
1367 }
1368+
1369+void TitlebarWindowManagerPolicy::advise_adding_to_workspace(
1370+ std::shared_ptr<Workspace> const& workspace, std::vector<Window> const& windows)
1371+{
1372+ if (windows.empty())
1373+ return;
1374+
1375+ for (auto const& window : windows)
1376+ {
1377+ if (workspace == active_workspace)
1378+ {
1379+ apply_workspace_visible_to(window);
1380+ }
1381+ else
1382+ {
1383+ apply_workspace_hidden_to(window);
1384+ }
1385+ }
1386+}
1387+
1388+void TitlebarWindowManagerPolicy::switch_workspace_to(
1389+ std::shared_ptr<Workspace> const& workspace,
1390+ Window const& window)
1391+{
1392+ if (workspace == active_workspace)
1393+ return;
1394+
1395+ auto const old_active = active_workspace;
1396+ active_workspace = workspace;
1397+
1398+ auto const old_active_window = tools.active_window();
1399+
1400+ if (!old_active_window)
1401+ {
1402+ // If there's no active window, the first shown grabs focus: get the right one
1403+ if (auto const ww = workspace_to_active[workspace])
1404+ {
1405+ tools.for_each_workspace_containing(ww, [&](std::shared_ptr<miral::Workspace> const& ws)
1406+ {
1407+ if (ws == workspace)
1408+ {
1409+ apply_workspace_visible_to(ww);
1410+ }
1411+ });
1412+ }
1413+ }
1414+
1415+ tools.remove_tree_from_workspace(window, old_active);
1416+ tools.add_tree_to_workspace(window, active_workspace);
1417+
1418+ tools.for_each_window_in_workspace(active_workspace, [&](Window const& window)
1419+ {
1420+ if (decoration_provider->is_decoration(window))
1421+ return; // decorations are taken care of automatically
1422+
1423+ apply_workspace_visible_to(window);
1424+ });
1425+
1426+ bool hide_old_active = false;
1427+ tools.for_each_window_in_workspace(old_active, [&](Window const& window)
1428+ {
1429+ if (decoration_provider->is_decoration(window))
1430+ return; // decorations are taken care of automatically
1431+
1432+ if (window == old_active_window)
1433+ {
1434+ // If we hide the active window focus will shift: do that last
1435+ hide_old_active = true;
1436+ return;
1437+ }
1438+
1439+ apply_workspace_hidden_to(window);
1440+ });
1441+
1442+ if (hide_old_active)
1443+ {
1444+ apply_workspace_hidden_to(old_active_window);
1445+
1446+ // Remember the old active_window when we switch away
1447+ workspace_to_active[old_active] = old_active_window;
1448+ }
1449+}
1450+
1451+void TitlebarWindowManagerPolicy::apply_workspace_hidden_to(Window const& window)
1452+{
1453+ auto const& window_info = tools.info_for(window);
1454+ auto& pdata = policy_data_for(window_info);
1455+ if (!pdata.in_hidden_workspace)
1456+ {
1457+ pdata.in_hidden_workspace = true;
1458+ pdata.old_state = window_info.state();
1459+
1460+ WindowSpecification modifications;
1461+ modifications.state() = mir_window_state_hidden;
1462+ tools.place_and_size_for_state(modifications, window_info);
1463+ tools.modify_window(window_info.window(), modifications);
1464+ }
1465+}
1466+
1467+void TitlebarWindowManagerPolicy::apply_workspace_visible_to(Window const& window)
1468+{
1469+ auto const& window_info = tools.info_for(window);
1470+ auto& pdata = policy_data_for(window_info);
1471+ if (pdata.in_hidden_workspace)
1472+ {
1473+ pdata.in_hidden_workspace = false;
1474+ WindowSpecification modifications;
1475+ modifications.state() = pdata.old_state;
1476+ tools.place_and_size_for_state(modifications, window_info);
1477+ tools.modify_window(window_info.window(), modifications);
1478+ }
1479+}
1480+
1481+void TitlebarWindowManagerPolicy::handle_modify_window(WindowInfo& window_info, WindowSpecification const& modifications)
1482+{
1483+ auto mods = modifications;
1484+
1485+ auto& pdata = policy_data_for(window_info);
1486+
1487+ if (pdata.in_hidden_workspace && mods.state().is_set())
1488+ pdata.old_state = mods.state().consume();
1489+
1490+ CanonicalWindowManagerPolicy::handle_modify_window(window_info, mods);
1491+}
1492+
1493
1494=== modified file 'miral-shell/titlebar_window_manager.h'
1495--- miral-shell/titlebar_window_manager.h 2017-01-26 11:30:51 +0000
1496+++ miral-shell/titlebar_window_manager.h 2017-03-03 10:23:15 +0000
1497@@ -1,5 +1,5 @@
1498 /*
1499- * Copyright © 2016 Canonical Ltd.
1500+ * Copyright © 2016-2017 Canonical Ltd.
1501 *
1502 * This program is free software: you can redistribute it and/or modify it
1503 * under the terms of the GNU General Public License version 3,
1504@@ -20,21 +20,27 @@
1505 #define MIRAL_SHELL_TITLEBAR_WINDOW_MANAGER_H
1506
1507 #include <miral/canonical_window_manager.h>
1508+#include <miral/workspace_policy.h>
1509
1510 #include "spinner/splash.h"
1511
1512 #include <chrono>
1513+#include <map>
1514
1515 namespace miral { class InternalClientLauncher; }
1516
1517 using namespace mir::geometry;
1518
1519-class TitlebarProvider;
1520+class DecorationProvider;
1521
1522-class TitlebarWindowManagerPolicy : public miral::CanonicalWindowManagerPolicy
1523+class TitlebarWindowManagerPolicy : public miral::CanonicalWindowManagerPolicy, miral::WorkspacePolicy
1524 {
1525 public:
1526- TitlebarWindowManagerPolicy(miral::WindowManagerTools const& tools, SpinnerSplash const& spinner, miral::InternalClientLauncher const& launcher);
1527+ TitlebarWindowManagerPolicy(
1528+ miral::WindowManagerTools const& tools,
1529+ SpinnerSplash const& spinner,
1530+ miral::InternalClientLauncher const& launcher,
1531+ std::function<void()>& shutdown_hook);
1532 ~TitlebarWindowManagerPolicy();
1533
1534 virtual miral::WindowSpecification place_new_window(
1535@@ -48,6 +54,8 @@
1536 * o Maximize/restore current window (to display size): Alt-F11
1537 * o Maximize/restore current window (to display height): Shift-F11
1538 * o Maximize/restore current window (to display width): Ctrl-F11
1539+ * o Switch workspace . . . . . . . . . . : Meta-Alt-[F1|F2|F3|F4]
1540+ * o Switch workspace taking active window: Meta-Ctrl-[F1|F2|F3|F4]
1541 * @{ */
1542 bool handle_pointer_event(MirPointerEvent const* event) override;
1543 bool handle_touch_event(MirTouchEvent const* event) override;
1544@@ -63,6 +71,8 @@
1545 void advise_state_change(miral::WindowInfo const& window_info, MirWindowState state) override;
1546 void advise_resize(miral::WindowInfo const& window_info, Size const& new_size) override;
1547 void advise_delete_window(miral::WindowInfo const& window_info) override;
1548+
1549+ void handle_modify_window(miral::WindowInfo& window_info, miral::WindowSpecification const& modifications) override;
1550 /** @} */
1551
1552 protected:
1553@@ -92,7 +102,7 @@
1554
1555 SpinnerSplash const spinner;
1556
1557- std::unique_ptr<TitlebarProvider> const titlebar_provider;
1558+ std::unique_ptr<DecorationProvider> const decoration_provider;
1559
1560 void end_resize();
1561
1562@@ -104,6 +114,23 @@
1563
1564 // Workaround for lp:1627697
1565 std::chrono::steady_clock::time_point last_resize;
1566+
1567+ void advise_adding_to_workspace(
1568+ std::shared_ptr<miral::Workspace> const& workspace,
1569+ std::vector<miral::Window> const& windows) override;
1570+
1571+ // Switch workspace, taking window (if not null)
1572+ void switch_workspace_to(
1573+ std::shared_ptr<miral::Workspace> const& workspace,
1574+ miral::Window const& window = miral::Window{});
1575+
1576+ std::shared_ptr<miral::Workspace> active_workspace;
1577+ std::map<int, std::shared_ptr<miral::Workspace>> key_to_workspace;
1578+ std::map<std::shared_ptr<miral::Workspace>, miral::Window> workspace_to_active;
1579+
1580+ void apply_workspace_visible_to(miral::Window const& window);
1581+
1582+ void apply_workspace_hidden_to(miral::Window const& window);
1583 };
1584
1585 #endif //MIRAL_SHELL_TITLEBAR_WINDOW_MANAGER_H
1586
1587=== modified file 'miral/CMakeLists.txt'
1588--- miral/CMakeLists.txt 2017-02-14 15:46:32 +0000
1589+++ miral/CMakeLists.txt 2017-03-03 10:23:15 +0000
1590@@ -21,7 +21,8 @@
1591 window_management_trace.cpp window_management_trace.h
1592 xcursor_loader.cpp xcursor_loader.h
1593 xcursor.c xcursor.h
1594- both_versions.h
1595+ both_versions.h
1596+ join_client_threads.h
1597 )
1598
1599 set_source_files_properties(xcursor.c PROPERTIES COMPILE_DEFINITIONS _GNU_SOURCE)
1600@@ -49,11 +50,13 @@
1601 set_command_line_hander.cpp ${CMAKE_SOURCE_DIR}/include/miral/set_command_line_hander.h
1602 set_terminator.cpp ${CMAKE_SOURCE_DIR}/include/miral/set_terminator.h
1603 set_window_managment_policy.cpp ${CMAKE_SOURCE_DIR}/include/miral/set_window_managment_policy.h
1604+ workspace_policy.cpp ${CMAKE_SOURCE_DIR}/include/miral/workspace_policy.h
1605 window_management_policy.cpp ${CMAKE_SOURCE_DIR}/include/miral/window_management_policy.h
1606 window_manager_tools.cpp ${CMAKE_SOURCE_DIR}/include/miral/window_manager_tools.h
1607 ${CMAKE_SOURCE_DIR}/include/mir/client/window_spec.h
1608 ${CMAKE_SOURCE_DIR}/include/mir/client/window_id.h
1609 ${CMAKE_SOURCE_DIR}/include/mir/client/connection.h
1610+ ${CMAKE_SOURCE_DIR}/include/mir/client/display_config.h
1611 ${CMAKE_SOURCE_DIR}/include/mir/client/window.h
1612 ${CMAKE_SOURCE_DIR}/include/mir/client/detail/mir_forward_compatibility.h
1613 )
1614@@ -100,9 +103,15 @@
1615 set(MIR_POINTER_CONFINEMENT 1)
1616 endif()
1617
1618+if (MIRSERVER_VERSION VERSION_LESS 0.22)
1619+ set(MIR_DISPLAY_CONFIG_GET_MUTABLE 0)
1620+else()
1621+ set(MIR_DISPLAY_CONFIG_GET_MUTABLE 1)
1622+endif()
1623+
1624 configure_file(
1625- ${CMAKE_CURRENT_SOURCE_DIR}/mir_features.h.in
1626- ${PROJECT_SOURCE_DIR}/include/mir/client/detail/mir_features.h
1627+ ${CMAKE_CURRENT_SOURCE_DIR}/mir_features.h.in
1628+ ${PROJECT_SOURCE_DIR}/include/mir/client/detail/mir_features.h
1629 )
1630
1631 configure_file(
1632
1633=== modified file 'miral/basic_window_manager.cpp'
1634--- miral/basic_window_manager.cpp 2017-02-08 17:17:41 +0000
1635+++ miral/basic_window_manager.cpp 2017-03-03 10:23:15 +0000
1636@@ -1,5 +1,5 @@
1637 /*
1638- * Copyright © 2015-2016 Canonical Ltd.
1639+ * Copyright © 2015-2017 Canonical Ltd.
1640 *
1641 * This program is free software: you can redistribute it and/or modify it
1642 * under the terms of the GNU General Public License version 3,
1643@@ -18,6 +18,7 @@
1644
1645 #include "basic_window_manager.h"
1646 #include "miral/window_manager_tools.h"
1647+#include "miral/workspace_policy.h"
1648
1649 #include <mir/scene/session.h>
1650 #include <mir/scene/surface.h>
1651@@ -37,15 +38,11 @@
1652 namespace
1653 {
1654 int const title_bar_height = 12;
1655+}
1656
1657-struct Locker
1658+struct miral::BasicWindowManager::Locker
1659 {
1660- Locker(std::mutex& mutex, std::unique_ptr<miral::WindowManagementPolicy> const& policy) :
1661- lock{mutex},
1662- policy{policy.get()}
1663- {
1664- policy->advise_begin();
1665- }
1666+ explicit Locker(miral::BasicWindowManager* self);
1667
1668 ~Locker()
1669 {
1670@@ -53,9 +50,53 @@
1671 }
1672
1673 std::lock_guard<std::mutex> const lock;
1674- miral::WindowManagementPolicy* const policy;
1675+ WindowManagementPolicy* const policy;
1676 };
1677-}
1678+
1679+miral::BasicWindowManager::Locker::Locker(BasicWindowManager* self) :
1680+ lock{self->mutex},
1681+ policy{self->policy.get()}
1682+{
1683+ policy->advise_begin();
1684+ std::vector<std::weak_ptr<Workspace>> workspaces;
1685+ {
1686+ std::lock_guard<std::mutex> const lock{self->dead_workspaces->dead_workspaces_mutex};
1687+ workspaces.swap(self->dead_workspaces->workspaces);
1688+ }
1689+
1690+ for (auto const& workspace : workspaces)
1691+ self->workspaces_to_windows.left.erase(workspace);
1692+}
1693+
1694+namespace
1695+{
1696+
1697+auto find_workspace_policy(std::unique_ptr<miral::WindowManagementPolicy> const& policy) -> miral::WorkspacePolicy*
1698+{
1699+ miral::WorkspacePolicy* result = dynamic_cast<miral::WorkspacePolicy*>(policy.get());
1700+
1701+ if (result)
1702+ return result;
1703+
1704+ struct NullWorkspacePolicy : miral::WorkspacePolicy
1705+ {
1706+ void advise_adding_to_workspace(
1707+ std::shared_ptr<miral::Workspace> const&, std::vector<miral::Window> const&) override
1708+ {
1709+ }
1710+
1711+ void advise_removing_from_workspace(
1712+ std::shared_ptr<miral::Workspace> const&, std::vector<miral::Window> const&) override
1713+ {
1714+ }
1715+ };
1716+
1717+ static NullWorkspacePolicy null_workspace_policy;
1718+
1719+ return &null_workspace_policy;
1720+}
1721+}
1722+
1723
1724 miral::BasicWindowManager::BasicWindowManager(
1725 shell::FocusController* focus_controller,
1726@@ -65,19 +106,20 @@
1727 focus_controller(focus_controller),
1728 display_layout(display_layout),
1729 persistent_surface_store{persistent_surface_store},
1730- policy(build(WindowManagerTools{this}))
1731+ policy(build(WindowManagerTools{this})),
1732+ workspace_policy{find_workspace_policy(policy)}
1733 {
1734 }
1735
1736 void miral::BasicWindowManager::add_session(std::shared_ptr<scene::Session> const& session)
1737 {
1738- Locker lock{mutex, policy};
1739+ Locker lock{this};
1740 policy->advise_new_app(app_info[session] = ApplicationInfo(session));
1741 }
1742
1743 void miral::BasicWindowManager::remove_session(std::shared_ptr<scene::Session> const& session)
1744 {
1745- Locker lock{mutex, policy};
1746+ Locker lock{this};
1747 policy->advise_delete_app(app_info[session]);
1748 app_info.erase(session);
1749 }
1750@@ -88,7 +130,7 @@
1751 std::function<frontend::SurfaceId(std::shared_ptr<scene::Session> const& session, scene::SurfaceCreationParameters const& params)> const& build)
1752 -> frontend::SurfaceId
1753 {
1754- Locker lock{mutex, policy};
1755+ Locker lock{this};
1756
1757 auto& session_info = info_for(session);
1758
1759@@ -112,6 +154,9 @@
1760 if (parent)
1761 info_for(parent).add_child(window);
1762
1763+ for_each_workspace_containing(parent,
1764+ [&](std::shared_ptr<miral::Workspace> const& workspace) { add_tree_to_workspace(window, workspace); });
1765+
1766 if (window_info.state() == mir_window_state_fullscreen)
1767 fullscreen_surfaces.insert(window_info.window());
1768
1769@@ -120,7 +165,7 @@
1770 std::shared_ptr<scene::Surface> const scene_surface = window_info.window();
1771 scene_surface->add_observer(std::make_shared<shell::SurfaceReadyObserver>(
1772 [this, &window_info](std::shared_ptr<scene::Session> const&, std::shared_ptr<scene::Surface> const&)
1773- { Locker lock{mutex, policy}; policy->handle_window_ready(window_info); },
1774+ { Locker lock{this}; policy->handle_window_ready(window_info); },
1775 session,
1776 scene_surface));
1777
1778@@ -141,7 +186,7 @@
1779 std::shared_ptr<scene::Surface> const& surface,
1780 shell::SurfaceSpecification const& modifications)
1781 {
1782- Locker lock{mutex, policy};
1783+ Locker lock{this};
1784 auto& info = info_for(surface);
1785 WindowSpecification mods{modifications};
1786 validate_modification_request(mods, info);
1787@@ -153,16 +198,28 @@
1788 std::shared_ptr<scene::Session> const& session,
1789 std::weak_ptr<scene::Surface> const& surface)
1790 {
1791- Locker lock{mutex, policy};
1792+ Locker lock{this};
1793 remove_window(session, info_for(surface));
1794 }
1795
1796 void miral::BasicWindowManager::remove_window(Application const& application, miral::WindowInfo const& info)
1797 {
1798+ bool const is_active_window{mru_active_windows.top() == info.window()};
1799+ auto const workspaces_containing_window = workspaces_containing(info.window());
1800+
1801+ {
1802+ std::vector<Window> const windows_removed{info.window()};
1803+
1804+ for (auto const& workspace : workspaces_containing_window)
1805+ {
1806+ workspace_policy->advise_removing_from_workspace(workspace, windows_removed);
1807+ }
1808+
1809+ workspaces_to_windows.right.erase(info.window());
1810+ }
1811+
1812 policy->advise_delete_window(info);
1813
1814- bool const is_active_window{mru_active_windows.top() == info.window()};
1815-
1816 info_for(application).remove_window(info.window());
1817 mru_active_windows.erase(info.window());
1818 fullscreen_surfaces.erase(info.window());
1819@@ -175,30 +232,66 @@
1820
1821 if (is_active_window)
1822 {
1823- // Try to make the parent active
1824- if (parent && select_active_window(parent))
1825- return;
1826-
1827- if (can_activate_window_for_session(application))
1828- return;
1829-
1830- // Try to activate to recently active window of any application
1831- {
1832- miral::Window new_focus;
1833-
1834- mru_active_windows.enumerate([&](miral::Window& window)
1835+ refocus(application, parent, workspaces_containing_window);
1836+ }
1837+}
1838+
1839+void miral::BasicWindowManager::refocus(
1840+ miral::Application const& application, miral::Window const& parent,
1841+ std::vector<std::shared_ptr<Workspace>> const& workspaces_containing_window)
1842+{
1843+ // Try to make the parent active
1844+ if (parent && select_active_window(parent))
1845+ return;
1846+
1847+ if (can_activate_window_for_session_in_workspace(application, workspaces_containing_window))
1848+ return;
1849+
1850+ // Try to activate to recently active window of any application in a shared workspace
1851+ {
1852+ miral::Window new_focus;
1853+
1854+ mru_active_windows.enumerate([&](miral::Window& window)
1855+ {
1856+ // select_active_window() calls set_focus_to() which updates mru_active_windows and changes window
1857+ auto const w = window;
1858+
1859+ for (auto const& workspace : workspaces_containing(w))
1860 {
1861- // select_active_window() calls set_focus_to() which updates mru_active_windows and changes window
1862- auto const w = window;
1863- return !(new_focus = select_active_window(w));
1864- });
1865-
1866- if (new_focus) return;
1867- }
1868-
1869- // Fallback to cycling through applications
1870- focus_next_application();
1871- }
1872+ for (auto const& ww : workspaces_containing_window)
1873+ {
1874+ if (ww == workspace)
1875+ {
1876+ return !(new_focus = select_active_window(w));
1877+ }
1878+ }
1879+ }
1880+
1881+ return true;
1882+ });
1883+
1884+ if (new_focus) return;
1885+ }
1886+
1887+ if (can_activate_window_for_session(application))
1888+ return;
1889+
1890+ // Try to activate to recently active window of any application
1891+ {
1892+ miral::Window new_focus;
1893+
1894+ mru_active_windows.enumerate([&](miral::Window& window)
1895+ {
1896+ // select_active_window() calls set_focus_to() which updates mru_active_windows and changes window
1897+ auto const w = window;
1898+ return !(new_focus = select_active_window(w));
1899+ });
1900+
1901+ if (new_focus) return;
1902+ }
1903+
1904+ // Fallback to cycling through applications
1905+ focus_next_application();
1906 }
1907
1908 void miral::BasicWindowManager::erase(miral::WindowInfo const& info)
1909@@ -214,7 +307,7 @@
1910
1911 void miral::BasicWindowManager::add_display(geometry::Rectangle const& area)
1912 {
1913- Locker lock{mutex, policy};
1914+ Locker lock{this};
1915 displays.add(area);
1916
1917 for (auto window : fullscreen_surfaces)
1918@@ -230,7 +323,7 @@
1919
1920 void miral::BasicWindowManager::remove_display(geometry::Rectangle const& area)
1921 {
1922- Locker lock{mutex, policy};
1923+ Locker lock{this};
1924 displays.remove(area);
1925 for (auto window : fullscreen_surfaces)
1926 {
1927@@ -245,21 +338,21 @@
1928
1929 bool miral::BasicWindowManager::handle_keyboard_event(MirKeyboardEvent const* event)
1930 {
1931- Locker lock{mutex, policy};
1932+ Locker lock{this};
1933 update_event_timestamp(event);
1934 return policy->handle_keyboard_event(event);
1935 }
1936
1937 bool miral::BasicWindowManager::handle_touch_event(MirTouchEvent const* event)
1938 {
1939- Locker lock{mutex, policy};
1940+ Locker lock{this};
1941 update_event_timestamp(event);
1942 return policy->handle_touch_event(event);
1943 }
1944
1945 bool miral::BasicWindowManager::handle_pointer_event(MirPointerEvent const* event)
1946 {
1947- Locker lock{mutex, policy};
1948+ Locker lock{this};
1949 update_event_timestamp(event);
1950
1951 cursor = {
1952@@ -274,7 +367,7 @@
1953 std::shared_ptr<scene::Surface> const& surface,
1954 uint64_t timestamp)
1955 {
1956- Locker lock{mutex, policy};
1957+ Locker lock{this};
1958 if (timestamp >= last_input_event_timestamp)
1959 policy->handle_raise_window(info_for(surface));
1960 }
1961@@ -315,7 +408,7 @@
1962 return surface->configure(attrib, value);
1963 }
1964
1965- Locker lock{mutex, policy};
1966+ Locker lock{this};
1967 auto& info = info_for(surface);
1968
1969 validate_modification_request(modification, info);
1970@@ -407,6 +500,28 @@
1971
1972 void miral::BasicWindowManager::focus_next_application()
1973 {
1974+ if (auto const prev = active_window())
1975+ {
1976+ auto const workspaces_containing_window = workspaces_containing(prev);
1977+
1978+ if (!workspaces_containing_window.empty())
1979+ {
1980+ do
1981+ {
1982+ focus_controller->focus_next_session();
1983+
1984+ if (can_activate_window_for_session_in_workspace(
1985+ focus_controller->focused_session(),
1986+ workspaces_containing_window))
1987+ {
1988+ return;
1989+ }
1990+ }
1991+ while (focus_controller->focused_session() != prev.application());
1992+ }
1993+
1994+ }
1995+
1996 focus_controller->focus_next_session();
1997
1998 if (can_activate_window_for_session(focus_controller->focused_session()))
1999@@ -417,15 +532,67 @@
2000 select_active_window(focussed_surface ? info_for(focussed_surface).window() : Window{});
2001 }
2002
2003+auto miral::BasicWindowManager::workspaces_containing(Window const& window) const
2004+-> std::vector<std::shared_ptr<Workspace>>
2005+{
2006+ auto const iter_pair = workspaces_to_windows.right.equal_range(window);
2007+
2008+ std::vector<std::shared_ptr<Workspace>> workspaces_containing_window;
2009+ for (auto kv = iter_pair.first; kv != iter_pair.second; ++kv)
2010+ {
2011+ if (auto const workspace = kv->second.lock())
2012+ {
2013+ workspaces_containing_window.push_back(workspace);
2014+ }
2015+ }
2016+
2017+ return workspaces_containing_window;
2018+}
2019+
2020 void miral::BasicWindowManager::focus_next_within_application()
2021 {
2022 if (auto const prev = active_window())
2023 {
2024+ auto const workspaces_containing_window = workspaces_containing(prev);
2025 auto const& siblings = info_for(prev.application()).windows();
2026 auto current = find(begin(siblings), end(siblings), prev);
2027
2028 if (current != end(siblings))
2029 {
2030+ while (++current != end(siblings))
2031+ {
2032+ for (auto const& workspace : workspaces_containing(*current))
2033+ {
2034+ for (auto const& ww : workspaces_containing_window)
2035+ {
2036+ if (ww == workspace)
2037+ {
2038+ if (prev != select_active_window(*current))
2039+ return;
2040+ }
2041+ }
2042+ }
2043+ }
2044+ }
2045+
2046+ for (current = begin(siblings); *current != prev; ++current)
2047+ {
2048+ for (auto const& workspace : workspaces_containing(*current))
2049+ {
2050+ for (auto const& ww : workspaces_containing_window)
2051+ {
2052+ if (ww == workspace)
2053+ {
2054+ if (prev != select_active_window(*current))
2055+ return;
2056+ }
2057+ }
2058+ }
2059+ }
2060+
2061+ current = find(begin(siblings), end(siblings), prev);
2062+ if (current != end(siblings))
2063+ {
2064 while (++current != end(siblings) && prev == select_active_window(*current))
2065 ;
2066 }
2067@@ -439,6 +606,63 @@
2068 }
2069 }
2070
2071+void miral::BasicWindowManager::focus_prev_within_application()
2072+{
2073+ if (auto const prev = active_window())
2074+ {
2075+ auto const workspaces_containing_window = workspaces_containing(prev);
2076+ auto const& siblings = info_for(prev.application()).windows();
2077+ auto current = find(rbegin(siblings), rend(siblings), prev);
2078+
2079+ if (current != rend(siblings))
2080+ {
2081+ while (++current != rend(siblings))
2082+ {
2083+ for (auto const& workspace : workspaces_containing(*current))
2084+ {
2085+ for (auto const& ww : workspaces_containing_window)
2086+ {
2087+ if (ww == workspace)
2088+ {
2089+ if (prev != select_active_window(*current))
2090+ return;
2091+ }
2092+ }
2093+ }
2094+ }
2095+ }
2096+
2097+ for (current = rbegin(siblings); *current != prev; ++current)
2098+ {
2099+ for (auto const& workspace : workspaces_containing(*current))
2100+ {
2101+ for (auto const& ww : workspaces_containing_window)
2102+ {
2103+ if (ww == workspace)
2104+ {
2105+ if (prev != select_active_window(*current))
2106+ return;
2107+ }
2108+ }
2109+ }
2110+ }
2111+
2112+ current = find(rbegin(siblings), rend(siblings), prev);
2113+ if (current != rend(siblings))
2114+ {
2115+ while (++current != rend(siblings) && prev == select_active_window(*current))
2116+ ;
2117+ }
2118+
2119+ if (current == rend(siblings))
2120+ {
2121+ current = rbegin(siblings);
2122+ while (prev != *current && prev == select_active_window(*current))
2123+ ++current;
2124+ }
2125+ }
2126+}
2127+
2128 auto miral::BasicWindowManager::window_at(geometry::Point cursor) const
2129 -> Window
2130 {
2131@@ -874,38 +1098,64 @@
2132 window_info.state() == mir_window_state_minimized;
2133
2134 policy->advise_state_change(window_info, value);
2135- window_info.state(value);
2136-
2137- mir_surface->configure(mir_window_attrib_state, value);
2138
2139 switch (value)
2140 {
2141 case mir_window_state_hidden:
2142 case mir_window_state_minimized:
2143- mir_surface->hide();
2144-
2145 if (window == active_window())
2146 {
2147+ auto const workspaces_containing_window = workspaces_containing(window);
2148+
2149 // Try to activate to recently active window of any application
2150 mru_active_windows.enumerate([&](Window& candidate)
2151 {
2152 if (candidate == window)
2153 return true;
2154 auto const w = candidate;
2155+ for (auto const& workspace : workspaces_containing(w))
2156+ {
2157+ for (auto const& ww : workspaces_containing_window)
2158+ {
2159+ if (ww == workspace)
2160+ {
2161+ return !(select_active_window(w));
2162+ }
2163+ }
2164+ }
2165+
2166+ return true;
2167+ });
2168+
2169+ // Try to activate to recently active window of any application
2170+ if (window == active_window() || !active_window())
2171+ mru_active_windows.enumerate([&](Window& candidate)
2172+ {
2173+ if (candidate == window)
2174+ return true;
2175+ auto const w = candidate;
2176 return !(select_active_window(w));
2177 });
2178
2179 if (window == active_window())
2180 select_active_window({});
2181-
2182- mru_active_windows.erase(window);
2183 }
2184+
2185+ window_info.state(value);
2186+ mir_surface->configure(mir_window_attrib_state, value);
2187+ mir_surface->hide();
2188+
2189 break;
2190
2191 default:
2192+ auto const none_active = !active_window();
2193+ window_info.state(value);
2194+ mir_surface->configure(mir_window_attrib_state, value);
2195 mir_surface->show();
2196- if (was_hidden && !active_window())
2197+ if (was_hidden && none_active)
2198+ {
2199 select_active_window(window);
2200+ }
2201 }
2202 }
2203
2204@@ -945,7 +1195,7 @@
2205
2206 void miral::BasicWindowManager::invoke_under_lock(std::function<void()> const& callback)
2207 {
2208- Locker lock{mutex, policy};
2209+ Locker lock{this};
2210 callback();
2211 }
2212
2213@@ -1076,6 +1326,35 @@
2214 return new_focus;
2215 }
2216
2217+auto miral::BasicWindowManager::can_activate_window_for_session_in_workspace(
2218+ Application const& session,
2219+ std::vector<std::shared_ptr<Workspace>> const& workspaces) -> bool
2220+{
2221+ miral::Window new_focus;
2222+
2223+ mru_active_windows.enumerate([&](miral::Window& window)
2224+ {
2225+ // select_active_window() calls set_focus_to() which updates mru_active_windows and changes window
2226+ auto const w = window;
2227+
2228+ if (w.application() != session)
2229+ return true;
2230+
2231+ for (auto const& workspace : workspaces_containing(w))
2232+ {
2233+ for (auto const& ww : workspaces)
2234+ {
2235+ if (ww == workspace)
2236+ return !(new_focus = select_active_window(w));
2237+ }
2238+ }
2239+
2240+ return true;
2241+ });
2242+
2243+ return new_focus;
2244+}
2245+
2246 auto miral::BasicWindowManager::place_new_surface(ApplicationInfo const& app_info, WindowSpecification parameters)
2247 -> WindowSpecification
2248 {
2249@@ -1610,3 +1889,173 @@
2250 BOOST_THROW_EXCEPTION(std::runtime_error("height must be positive"));
2251 }
2252 }
2253+
2254+class miral::Workspace
2255+{
2256+public:
2257+ explicit Workspace(std::shared_ptr<miral::BasicWindowManager::DeadWorkspaces> const& dead_workspaces) :
2258+ dead_workspaces{dead_workspaces} {}
2259+
2260+ std::weak_ptr<Workspace> self;
2261+
2262+ ~Workspace()
2263+ {
2264+ std::lock_guard<std::mutex> lock {dead_workspaces->dead_workspaces_mutex};
2265+ dead_workspaces->workspaces.push_back(self);
2266+ }
2267+
2268+private:
2269+ std::shared_ptr<miral::BasicWindowManager::DeadWorkspaces> const dead_workspaces;
2270+};
2271+
2272+auto miral::BasicWindowManager::create_workspace() -> std::shared_ptr<Workspace>
2273+{
2274+ auto const result = std::make_shared<Workspace>(dead_workspaces);
2275+ result->self = result;
2276+ return result;
2277+}
2278+
2279+void miral::BasicWindowManager::add_tree_to_workspace(
2280+ miral::Window const& window, std::shared_ptr<miral::Workspace> const& workspace)
2281+{
2282+ if (!window) return;
2283+
2284+ auto root = window;
2285+ auto const* info = &info_for(root);
2286+
2287+ while (auto const& parent = info->parent())
2288+ {
2289+ root = parent;
2290+ info = &info_for(root);
2291+ }
2292+
2293+ std::vector<Window> windows;
2294+
2295+ std::function<void(WindowInfo const& info)> const add_children =
2296+ [&,this](WindowInfo const& info)
2297+ {
2298+ for (auto const& child : info.children())
2299+ {
2300+ windows.push_back(child);
2301+ add_children(info_for(child));
2302+ }
2303+ };
2304+
2305+ windows.push_back(root);
2306+ add_children(*info);
2307+
2308+ auto const iter_pair = workspaces_to_windows.left.equal_range(workspace);
2309+
2310+ std::vector<Window> windows_added;
2311+
2312+ for (auto& w : windows)
2313+ {
2314+ if (!std::count_if(iter_pair.first, iter_pair.second,
2315+ [&w](wwbimap_t::left_value_type const& kv) { return kv.second == w; }))
2316+ {
2317+ workspaces_to_windows.left.insert(wwbimap_t::left_value_type{workspace, w});
2318+ windows_added.push_back(w);
2319+ }
2320+ }
2321+
2322+ if (!windows_added.empty())
2323+ workspace_policy->advise_adding_to_workspace(workspace, windows_added);
2324+}
2325+
2326+void miral::BasicWindowManager::remove_tree_from_workspace(
2327+ miral::Window const& window, std::shared_ptr<miral::Workspace> const& workspace)
2328+{
2329+ if (!window) return;
2330+
2331+ auto root = window;
2332+ auto const* info = &info_for(root);
2333+
2334+ while (auto const& parent = info->parent())
2335+ {
2336+ root = parent;
2337+ info = &info_for(root);
2338+ }
2339+
2340+ std::vector<Window> windows;
2341+
2342+ std::function<void(WindowInfo const& info)> const add_children =
2343+ [&,this](WindowInfo const& info)
2344+ {
2345+ for (auto const& child : info.children())
2346+ {
2347+ windows.push_back(child);
2348+ add_children(info_for(child));
2349+ }
2350+ };
2351+
2352+ windows.push_back(root);
2353+ add_children(*info);
2354+
2355+ std::vector<Window> windows_removed;
2356+
2357+ auto const iter_pair = workspaces_to_windows.left.equal_range(workspace);
2358+ for (auto kv = iter_pair.first; kv != iter_pair.second;)
2359+ {
2360+ auto const current = kv++;
2361+ if (std::count(begin(windows), end(windows), current->second))
2362+ {
2363+ windows_removed.push_back(current->second);
2364+ workspaces_to_windows.left.erase(current);
2365+ }
2366+ }
2367+
2368+ if (!windows_removed.empty())
2369+ workspace_policy->advise_removing_from_workspace(workspace, windows_removed);
2370+}
2371+
2372+void miral::BasicWindowManager::move_workspace_content_to_workspace(
2373+ std::shared_ptr<Workspace> const& to_workspace, std::shared_ptr<Workspace> const& from_workspace)
2374+{
2375+ std::vector<Window> windows_removed;
2376+
2377+ auto const iter_pair_from = workspaces_to_windows.left.equal_range(from_workspace);
2378+ for (auto kv = iter_pair_from.first; kv != iter_pair_from.second;)
2379+ {
2380+ auto const current = kv++;
2381+ windows_removed.push_back(current->second);
2382+ workspaces_to_windows.left.erase(current);
2383+ }
2384+
2385+ if (!windows_removed.empty())
2386+ workspace_policy->advise_removing_from_workspace(from_workspace, windows_removed);
2387+
2388+ std::vector<Window> windows_added;
2389+
2390+ auto const iter_pair_to = workspaces_to_windows.left.equal_range(to_workspace);
2391+ for (auto& w : windows_removed)
2392+ {
2393+ if (!std::count_if(iter_pair_to.first, iter_pair_to.second,
2394+ [&w](wwbimap_t::left_value_type const& kv) { return kv.second == w; }))
2395+ {
2396+ workspaces_to_windows.left.insert(wwbimap_t::left_value_type{to_workspace, w});
2397+ windows_added.push_back(w);
2398+ }
2399+ }
2400+
2401+ if (!windows_added.empty())
2402+ workspace_policy->advise_adding_to_workspace(to_workspace, windows_added);
2403+}
2404+
2405+void miral::BasicWindowManager::for_each_workspace_containing(
2406+ miral::Window const& window, std::function<void(std::shared_ptr<miral::Workspace> const&)> const& callback)
2407+{
2408+ auto const iter_pair = workspaces_to_windows.right.equal_range(window);
2409+ for (auto kv = iter_pair.first; kv != iter_pair.second; ++kv)
2410+ {
2411+ if (auto const workspace = kv->second.lock())
2412+ callback(workspace);
2413+ }
2414+}
2415+
2416+void miral::BasicWindowManager::for_each_window_in_workspace(
2417+ std::shared_ptr<miral::Workspace> const& workspace, std::function<void(miral::Window const&)> const& callback)
2418+{
2419+ auto const iter_pair = workspaces_to_windows.left.equal_range(workspace);
2420+ for (auto kv = iter_pair.first; kv != iter_pair.second; ++kv)
2421+ callback(kv->second);
2422+}
2423
2424=== modified file 'miral/basic_window_manager.h'
2425--- miral/basic_window_manager.h 2017-02-02 17:18:42 +0000
2426+++ miral/basic_window_manager.h 2017-03-03 10:23:15 +0000
2427@@ -1,5 +1,5 @@
2428 /*
2429- * Copyright © 2015-2016 Canonical Ltd.
2430+ * Copyright © 2015-2017 Canonical Ltd.
2431 *
2432 * This program is free software: you can redistribute it and/or modify it
2433 * under the terms of the GNU General Public License version 3,
2434@@ -31,6 +31,9 @@
2435 #include <mir/shell/abstract_shell.h>
2436 #include <mir/shell/window_manager.h>
2437
2438+#include <boost/bimap.hpp>
2439+#include <boost/bimap/multiset_of.hpp>
2440+
2441 #include <map>
2442 #include <mutex>
2443
2444@@ -41,6 +44,7 @@
2445
2446 namespace miral
2447 {
2448+class WorkspacePolicy;
2449 using mir::shell::SurfaceSet;
2450 using WindowManagementPolicyBuilder =
2451 std::function<std::unique_ptr<miral::WindowManagementPolicy>(miral::WindowManagerTools const& tools)>;
2452@@ -97,6 +101,23 @@
2453 MirWindowAttrib attrib,
2454 int value) override;
2455
2456+ auto create_workspace() -> std::shared_ptr<Workspace> override;
2457+
2458+ void add_tree_to_workspace(Window const& window, std::shared_ptr<Workspace> const& workspace) override;
2459+
2460+ void remove_tree_from_workspace(Window const& window, std::shared_ptr<Workspace> const& workspace) override;
2461+
2462+ void move_workspace_content_to_workspace(
2463+ std::shared_ptr<Workspace> const& to_workspace,
2464+ std::shared_ptr<Workspace> const& from_workspace);
2465+
2466+ void for_each_workspace_containing(
2467+ Window const& window,
2468+ std::function<void(std::shared_ptr<Workspace> const& workspace)> const& callback) override;
2469+
2470+ void for_each_window_in_workspace(
2471+ std::shared_ptr<Workspace> const& workspace, std::function<void(Window const&)> const& callback) override;
2472+
2473 auto count_applications() const -> unsigned int override;
2474
2475 void for_each_application(std::function<void(ApplicationInfo& info)> const& functor) override;
2476@@ -125,6 +146,7 @@
2477 void focus_next_application() override;
2478
2479 void focus_next_within_application() override;
2480+ void focus_prev_within_application() override;
2481
2482 auto window_at(mir::geometry::Point cursor) const -> Window override;
2483
2484@@ -148,7 +170,18 @@
2485 mir::shell::FocusController* const focus_controller;
2486 std::shared_ptr<mir::shell::DisplayLayout> const display_layout;
2487 std::shared_ptr<mir::shell::PersistentSurfaceStore> const persistent_surface_store;
2488+
2489+ // Workspaces may die without any sync with the BWM mutex
2490+ struct DeadWorkspaces
2491+ {
2492+ std::mutex mutable dead_workspaces_mutex;
2493+ std::vector<std::weak_ptr<Workspace>> workspaces;
2494+ };
2495+
2496+ std::shared_ptr<DeadWorkspaces> const dead_workspaces{std::make_shared<DeadWorkspaces>()};
2497+
2498 std::unique_ptr<WindowManagementPolicy> const policy;
2499+ WorkspacePolicy* const workspace_policy;
2500
2501 std::mutex mutex;
2502 SessionInfoMap app_info;
2503@@ -160,11 +193,23 @@
2504 using FullscreenSurfaces = std::set<Window>;
2505 FullscreenSurfaces fullscreen_surfaces;
2506
2507+ friend class Workspace;
2508+ using wwbimap_t = boost::bimap<
2509+ boost::bimaps::multiset_of<std::weak_ptr<Workspace>, std::owner_less<std::weak_ptr<Workspace>>>,
2510+ boost::bimaps::multiset_of<Window>>;
2511+
2512+ wwbimap_t workspaces_to_windows;
2513+
2514+ struct Locker;
2515+
2516 void update_event_timestamp(MirKeyboardEvent const* kev);
2517 void update_event_timestamp(MirPointerEvent const* pev);
2518 void update_event_timestamp(MirTouchEvent const* tev);
2519
2520 auto can_activate_window_for_session(miral::Application const& session) -> bool;
2521+ auto can_activate_window_for_session_in_workspace(
2522+ miral::Application const& session,
2523+ std::vector<std::shared_ptr<Workspace>> const& workspaces) -> bool;
2524
2525 auto place_new_surface(ApplicationInfo const& app_info, WindowSpecification parameters) -> WindowSpecification;
2526 auto place_relative(mir::geometry::Rectangle const& parent, miral::WindowSpecification const& parameters, Size size)
2527@@ -177,6 +222,9 @@
2528 void set_state(miral::WindowInfo& window_info, MirWindowState value);
2529 auto fullscreen_rect_for(WindowInfo const& window_info) const -> Rectangle;
2530 void remove_window(Application const& application, miral::WindowInfo const& info);
2531+ void refocus(Application const& application, Window const& parent,
2532+ std::vector<std::shared_ptr<Workspace>> const& workspaces_containing_window);
2533+ auto workspaces_containing(Window const& window) const -> std::vector<std::shared_ptr<Workspace>>;
2534 };
2535 }
2536
2537
2538=== modified file 'miral/internal_client.cpp'
2539--- miral/internal_client.cpp 2017-02-15 12:17:12 +0000
2540+++ miral/internal_client.cpp 2017-03-03 10:23:15 +0000
2541@@ -1,5 +1,5 @@
2542 /*
2543- * Copyright © 2016 Canonical Ltd.
2544+ * Copyright © 2016-2017 Canonical Ltd.
2545 *
2546 * This program is free software: you can redistribute it and/or modify it
2547 * under the terms of the GNU General Public License version 3,
2548@@ -17,6 +17,7 @@
2549 */
2550
2551 #include "miral/internal_client.h"
2552+#include "join_client_threads.h"
2553 #include "both_versions.h"
2554
2555 #include <mir/fd.h>
2556@@ -24,10 +25,14 @@
2557 #include <mir/scene/session.h>
2558 #include <mir/main_loop.h>
2559
2560+#define MIR_LOG_COMPONENT "miral::Internal Client"
2561+#include <mir/log.h>
2562+
2563 #include <atomic>
2564 #include <condition_variable>
2565 #include <mutex>
2566 #include <thread>
2567+#include <map>
2568
2569 namespace
2570 {
2571@@ -39,6 +44,8 @@
2572 std::function<void(std::weak_ptr<mir::scene::Session> const session)> connect_notification);
2573
2574 void run(mir::Server& server);
2575+ void join_client_thread();
2576+
2577 ~InternalClientRunner();
2578
2579 private:
2580@@ -52,13 +59,34 @@
2581 std::function<void(mir::client::Connection connection)> const client_code;
2582 std::function<void(std::weak_ptr<mir::scene::Session> const session)> connect_notification;
2583 };
2584-}
2585-
2586-class miral::StartupInternalClient::Self : InternalClientRunner
2587-{
2588-public:
2589+
2590+std::mutex client_runners_mutex;
2591+std::multimap<mir::Server*, std::weak_ptr<InternalClientRunner>> client_runners;
2592+
2593+void register_runner(mir::Server* server, std::weak_ptr<InternalClientRunner> internal_client)
2594+{
2595+ std::lock_guard<decltype(client_runners_mutex)> lock{client_runners_mutex};
2596+ client_runners.emplace(server, std::move(internal_client));
2597+}
2598+
2599+void join_runners_for(mir::Server* server)
2600+{
2601+ std::lock_guard<decltype(client_runners_mutex)> lock{client_runners_mutex};
2602+ auto range = client_runners.equal_range(server);
2603+
2604+ for (auto i = range.first; i != range.second; ++i)
2605+ {
2606+ if (auto runner = i->second.lock())
2607+ runner->join_client_thread();
2608+ }
2609+
2610+ client_runners.erase(range.first, range.second);
2611+}
2612+}
2613+
2614+class miral::StartupInternalClient::Self : public InternalClientRunner
2615+{
2616 using InternalClientRunner::InternalClientRunner;
2617- using InternalClientRunner::run;
2618 };
2619
2620 InternalClientRunner::InternalClientRunner(
2621@@ -86,6 +114,16 @@
2622
2623 connection = mir::client::Connection{mir_connect_sync(connect_string, name.c_str())};
2624
2625+ mir_connection_set_lifecycle_event_callback(
2626+ connection,
2627+ [](MirConnection*, MirLifecycleState transition, void*)
2628+ {
2629+ // The default handler kills the process with SIGHUP - which is unhelpful for internal clients
2630+ if (transition == mir_lifecycle_connection_lost)
2631+ mir::log_warning("server disconnected before connection released by client");
2632+ },
2633+ this);
2634+
2635 std::unique_lock<decltype(mutex)> lock{mutex};
2636 cv.wait(lock, [&] { return !!session.lock(); });
2637
2638@@ -98,6 +136,12 @@
2639
2640 InternalClientRunner::~InternalClientRunner()
2641 {
2642+ join_client_thread();
2643+}
2644+
2645+
2646+void InternalClientRunner::join_client_thread()
2647+{
2648 if (thread.joinable())
2649 {
2650 thread.join();
2651@@ -124,6 +168,8 @@
2652
2653 void miral::StartupInternalClient::operator()(mir::Server& server)
2654 {
2655+ register_runner(&server, internal_client);
2656+
2657 server.add_init_callback([this, &server]
2658 {
2659 server.the_main_loop()->enqueue(this, [this, &server]
2660@@ -138,7 +184,7 @@
2661 struct miral::InternalClientLauncher::Self
2662 {
2663 mir::Server* server = nullptr;
2664- std::unique_ptr<InternalClientRunner> runner;
2665+ std::shared_ptr<InternalClientRunner> runner;
2666 };
2667
2668 void miral::InternalClientLauncher::operator()(mir::Server& server)
2669@@ -155,9 +201,15 @@
2670 std::function<void(mir::client::Connection connection)> const& client_code,
2671 std::function<void(std::weak_ptr<mir::scene::Session> const session)> const& connect_notification) const
2672 {
2673- self->runner = std::make_unique<InternalClientRunner>(name, client_code, connect_notification);
2674+ self->runner = std::make_shared<InternalClientRunner>(name, client_code, connect_notification);
2675 self->server->the_main_loop()->enqueue(this, [this] { self->runner->run(*self->server); });
2676+ register_runner(self->server, self->runner);
2677 }
2678
2679 miral::InternalClientLauncher::InternalClientLauncher() : self{std::make_shared<Self>()} {}
2680 miral::InternalClientLauncher::~InternalClientLauncher() = default;
2681+
2682+void join_client_threads(mir::Server* server)
2683+{
2684+ join_runners_for(server);
2685+}
2686
2687=== added file 'miral/join_client_threads.h'
2688--- miral/join_client_threads.h 1970-01-01 00:00:00 +0000
2689+++ miral/join_client_threads.h 2017-03-03 10:23:15 +0000
2690@@ -0,0 +1,24 @@
2691+/*
2692+ * Copyright © 2017 Canonical Ltd.
2693+ *
2694+ * This program is free software: you can redistribute it and/or modify it
2695+ * under the terms of the GNU General Public License version 3,
2696+ * as published by the Free Software Foundation.
2697+ *
2698+ * This program is distributed in the hope that it will be useful,
2699+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2700+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2701+ * GNU General Public License for more details.
2702+ *
2703+ * You should have received a copy of the GNU General Public License
2704+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2705+ *
2706+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
2707+ */
2708+
2709+#ifndef MIRAL_JOIN_CLIENT_THREADS_H
2710+#define MIRAL_JOIN_CLIENT_THREADS_H
2711+
2712+void join_client_threads(mir::Server* server);
2713+
2714+#endif //MIRAL_JOIN_CLIENT_THREADS_H
2715
2716=== modified file 'miral/mir_features.h.in'
2717--- miral/mir_features.h.in 2016-09-23 13:59:34 +0000
2718+++ miral/mir_features.h.in 2017-03-03 10:23:15 +0000
2719@@ -24,5 +24,6 @@
2720 // ============================================================
2721
2722 #define MIRAL_MIR_DEFINES_POINTER_CONFINEMENT (@MIR_POINTER_CONFINEMENT@)
2723+#define MIR_DEFINES_DISPLAY_CONFIG_GET_MUTABLE_OUTPUT (@MIR_DISPLAY_CONFIG_GET_MUTABLE@)
2724
2725 #endif // MIRAL_MIR_FEATURES_H
2726
2727=== modified file 'miral/mru_window_list.cpp'
2728--- miral/mru_window_list.cpp 2017-02-02 11:31:52 +0000
2729+++ miral/mru_window_list.cpp 2017-03-03 10:23:15 +0000
2730@@ -18,8 +18,28 @@
2731
2732 #include "mru_window_list.h"
2733
2734+#include <mir/client/detail/mir_forward_compatibility.h>
2735+#include <mir/scene/surface.h>
2736+
2737 #include <algorithm>
2738
2739+namespace
2740+{
2741+bool visible(miral::Window const& window)
2742+{
2743+ std::shared_ptr<mir::scene::Surface> const& surface{window};
2744+
2745+ switch (surface->state())
2746+ {
2747+ case mir_window_state_hidden:
2748+ case mir_window_state_minimized:
2749+ return false;
2750+ default:
2751+ return surface->visible();
2752+ }
2753+}
2754+}
2755+
2756 void miral::MRUWindowList::push(Window const& window)
2757 {
2758 windows.erase(remove(begin(windows), end(windows), window), end(windows));
2759@@ -33,12 +53,14 @@
2760
2761 auto miral::MRUWindowList::top() const -> Window
2762 {
2763- return (!windows.empty()) ? windows.back() : Window{};
2764+ auto const& found = std::find_if(rbegin(windows), rend(windows), visible);
2765+ return (found != rend(windows)) ? *found: Window{};
2766 }
2767
2768 void miral::MRUWindowList::enumerate(Enumerator const& enumerator) const
2769 {
2770 for (auto i = windows.rbegin(); i != windows.rend(); ++i)
2771- if (!enumerator(const_cast<Window&>(*i)))
2772- break;
2773+ if (visible(*i))
2774+ if (!enumerator(const_cast<Window&>(*i)))
2775+ break;
2776 }
2777
2778=== modified file 'miral/runner.cpp'
2779--- miral/runner.cpp 2016-12-01 11:48:56 +0000
2780+++ miral/runner.cpp 2017-03-03 10:23:15 +0000
2781@@ -1,5 +1,5 @@
2782 /*
2783- * Copyright © 2016 Canonical Ltd.
2784+ * Copyright © 2016-2017 Canonical Ltd.
2785 *
2786 * This program is free software: you can redistribute it and/or modify it
2787 * under the terms of the GNU General Public License version 3,
2788@@ -17,6 +17,7 @@
2789 */
2790
2791 #include "miral/runner.h"
2792+#include "join_client_threads.h"
2793
2794 #include <mir/server.h>
2795 #include <mir/main_loop.h>
2796@@ -29,6 +30,10 @@
2797 #include <mutex>
2798 #include <thread>
2799
2800+#if MIR_SERVER_VERSION < MIR_VERSION_NUMBER(0, 24, 0)
2801+#include <csignal>
2802+#endif
2803+
2804 namespace
2805 {
2806 inline auto filename(std::string path) -> std::string
2807@@ -50,7 +55,7 @@
2808
2809 std::mutex mutex;
2810 std::function<void()> start_callback{[]{}};
2811- std::function<void()> stop_callback{[]{}};
2812+ std::function<void()> stop_callback{[this]{ join_client_threads(weak_server.lock().get()); }};
2813 std::function<void()> exception_handler{static_cast<void(*)()>(mir::report_exception)};
2814 std::weak_ptr<mir::Server> weak_server;
2815 };
2816@@ -215,6 +220,10 @@
2817 auto const main_loop = server->the_main_loop();
2818 main_loop->enqueue(this, start_callback);
2819
2820+#if MIR_SERVER_VERSION < MIR_VERSION_NUMBER(0, 24, 0)
2821+ main_loop->register_signal_handler({SIGINT, SIGTERM}, [this](int) {stop_callback();});
2822+#endif
2823+
2824 server->run();
2825 }
2826
2827
2828=== modified file 'miral/symbols.map'
2829--- miral/symbols.map 2017-02-15 12:06:25 +0000
2830+++ miral/symbols.map 2017-03-03 10:23:15 +0000
2831@@ -367,3 +367,26 @@
2832 vtable?for?miral::ApplicationAuthorizer1;
2833 };
2834 } MIRAL_1.1;
2835+
2836+MIRAL_1.3 {
2837+global:
2838+ extern "C++" {
2839+ miral::WindowManagerTools::add_tree_to_workspace*;
2840+ miral::WindowManagerTools::create_workspace*;
2841+ miral::WindowManagerTools::focus_prev_within_application*;
2842+ miral::WindowManagerTools::for_each_window_in_workspace*;
2843+ miral::WindowManagerTools::for_each_workspace_containing*;
2844+ miral::WindowManagerTools::remove_tree_from_workspace*;
2845+ miral::WindowManagerTools::move_workspace_content_to_workspace*;
2846+ miral::WorkspacePolicy::?WorkspacePolicy*;
2847+ miral::WorkspacePolicy::WorkspacePolicy*;
2848+ miral::WorkspacePolicy::advise_adding_to_workspace*;
2849+ miral::WorkspacePolicy::advise_removing_from_workspace*;
2850+ miral::WorkspacePolicy::operator*;
2851+ non-virtual?thunk?to?miral::WorkspacePolicy::?WorkspacePolicy*;
2852+ non-virtual?thunk?to?miral::WorkspacePolicy::advise_adding_to_workspace*;
2853+ non-virtual?thunk?to?miral::WorkspacePolicy::advise_removing_from_workspace*;
2854+ typeinfo?for?miral::WorkspacePolicy;
2855+ vtable?for?miral::WorkspacePolicy;
2856+ };
2857+} MIRAL_1.2;
2858
2859=== modified file 'miral/window.cpp'
2860--- miral/window.cpp 2016-08-05 08:53:45 +0000
2861+++ miral/window.cpp 2017-03-03 10:23:15 +0000
2862@@ -120,3 +120,8 @@
2863 {
2864 return rhs == lhs;
2865 }
2866+
2867+bool miral::operator<(Window const& lhs, Window const& rhs)
2868+{
2869+ return lhs.self.owner_before(rhs.self);
2870+}
2871
2872=== modified file 'miral/window_management_trace.cpp'
2873--- miral/window_management_trace.cpp 2017-02-02 17:18:42 +0000
2874+++ miral/window_management_trace.cpp 2017-03-03 10:23:15 +0000
2875@@ -25,6 +25,7 @@
2876 #include <mir/scene/surface.h>
2877 #include <mir/event_printer.h>
2878
2879+#include <iomanip>
2880 #include <sstream>
2881
2882 #define MIR_LOG_COMPONENT "miral::Window Management"
2883@@ -73,6 +74,22 @@
2884 first_field = false;
2885 return *this;
2886 }
2887+
2888+ auto append(char const* name, MirOrientationMode item) const -> BracedItemStream const&
2889+ {
2890+ auto const flags = out.flags();
2891+ auto const prec = out.precision();
2892+ auto const fill = out.fill();
2893+
2894+ if (!first_field) out << ", ";
2895+ out << name << '=' << std::showbase << std::internal << std::setfill('0') << std::setw(2) << std::hex << item;
2896+ first_field = false;
2897+
2898+ out.flags(flags);
2899+ out.precision(prec);
2900+ out.fill(fill);
2901+ return *this;
2902+ }
2903 };
2904
2905 auto operator<< (std::ostream& out, miral::WindowSpecification::AspectRatio const& ratio) -> std::ostream&
2906@@ -99,8 +116,15 @@
2907
2908 auto dump_of(std::vector<miral::Window> const& windows) -> std::string;
2909
2910+inline auto operator!=(miral::WindowInfo::AspectRatio const& lhs, miral::WindowInfo::AspectRatio const& rhs)
2911+{
2912+ return lhs.width != rhs.width || lhs.height != rhs.height;
2913+}
2914+
2915 auto dump_of(miral::WindowInfo const& info) -> std::string
2916 {
2917+ using namespace mir::geometry;
2918+
2919 std::stringstream out;
2920 {
2921 BracedItemStream bout{out};
2922@@ -109,18 +133,18 @@
2923 APPEND(name);
2924 APPEND(type);
2925 APPEND(state);
2926- APPEND(restore_rect);
2927+ if (info.state() != mir_window_state_restored) APPEND(restore_rect);
2928 if (std::shared_ptr<mir::scene::Surface> parent = info.parent())
2929 bout.append("parent", parent->name());
2930 bout.append("children", dump_of(info.children()));
2931- APPEND(min_width);
2932- APPEND(min_height);
2933- APPEND(max_width);
2934- APPEND(max_height);
2935- APPEND(width_inc);
2936- APPEND(height_inc);
2937- APPEND(min_aspect);
2938- APPEND(max_aspect);
2939+ if (info.min_width() != Width{0}) APPEND(min_width);
2940+ if (info.min_height() != Height{0}) APPEND(min_height);
2941+ if (info.max_width() != Width{std::numeric_limits<int>::max()}) APPEND(max_width);
2942+ if (info.max_height() != Height{std::numeric_limits<int>::max()}) APPEND(max_height);
2943+ if (info.width_inc() != DeltaX{1}) APPEND(width_inc);
2944+ if (info.height_inc() != DeltaY{1}) APPEND(height_inc);
2945+ if (info.min_aspect() != miral::WindowInfo::AspectRatio{0U, std::numeric_limits<unsigned>::max()}) APPEND(min_aspect);
2946+ if (info.max_aspect() != miral::WindowInfo::AspectRatio{std::numeric_limits<unsigned>::max(), 0U}) APPEND(max_aspect);
2947 APPEND(preferred_orientation);
2948 APPEND(confine_pointer);
2949
2950@@ -505,6 +529,15 @@
2951 }
2952 MIRAL_TRACE_EXCEPTION
2953
2954+void miral::WindowManagementTrace::focus_prev_within_application()
2955+try {
2956+ log_input();
2957+ mir::log_info("%s", __func__);
2958+ trace_count++;
2959+ wrapped.focus_prev_within_application();
2960+}
2961+MIRAL_TRACE_EXCEPTION
2962+
2963 void miral::WindowManagementTrace::raise_tree(miral::Window const& root)
2964 try {
2965 log_input();
2966@@ -532,6 +565,53 @@
2967 }
2968 MIRAL_TRACE_EXCEPTION
2969
2970+auto miral::WindowManagementTrace::create_workspace() -> std::shared_ptr<Workspace>
2971+try {
2972+ mir::log_info("%s", __func__);
2973+ return wrapped.create_workspace();
2974+}
2975+MIRAL_TRACE_EXCEPTION
2976+
2977+void miral::WindowManagementTrace::add_tree_to_workspace(
2978+ miral::Window const& window, std::shared_ptr<miral::Workspace> const& workspace)
2979+try {
2980+ mir::log_info("%s window=%s, workspace =%p", __func__, dump_of(window).c_str(), workspace.get());
2981+ wrapped.add_tree_to_workspace(window, workspace);
2982+}
2983+MIRAL_TRACE_EXCEPTION
2984+
2985+void miral::WindowManagementTrace::remove_tree_from_workspace(
2986+ miral::Window const& window, std::shared_ptr<miral::Workspace> const& workspace)
2987+try {
2988+ mir::log_info("%s window=%s, workspace =%p", __func__, dump_of(window).c_str(), workspace.get());
2989+ wrapped.remove_tree_from_workspace(window, workspace);
2990+}
2991+MIRAL_TRACE_EXCEPTION
2992+
2993+void miral::WindowManagementTrace::move_workspace_content_to_workspace(
2994+ std::shared_ptr<Workspace> const& to_workspace, std::shared_ptr<Workspace> const& from_workspace)
2995+try {
2996+ mir::log_info("%s to_workspace=%p, from_workspace=%p", __func__, to_workspace.get(), from_workspace.get());
2997+ wrapped.move_workspace_content_to_workspace(to_workspace, from_workspace);
2998+}
2999+MIRAL_TRACE_EXCEPTION
3000+
3001+void miral::WindowManagementTrace::for_each_workspace_containing(
3002+ miral::Window const& window, std::function<void(std::shared_ptr<miral::Workspace> const&)> const& callback)
3003+try {
3004+ mir::log_info("%s window=%s", __func__, dump_of(window).c_str());
3005+ wrapped.for_each_workspace_containing(window, callback);
3006+}
3007+MIRAL_TRACE_EXCEPTION
3008+
3009+void miral::WindowManagementTrace::for_each_window_in_workspace(
3010+ std::shared_ptr<miral::Workspace> const& workspace, std::function<void(miral::Window const&)> const& callback)
3011+try {
3012+ mir::log_info("%s workspace =%p", __func__, workspace.get());
3013+ wrapped.for_each_window_in_workspace(workspace, callback);
3014+}
3015+MIRAL_TRACE_EXCEPTION
3016+
3017 auto miral::WindowManagementTrace::place_new_window(
3018 ApplicationInfo const& app_info,
3019 WindowSpecification const& requested_specification) -> WindowSpecification
3020
3021=== modified file 'miral/window_management_trace.h'
3022--- miral/window_management_trace.h 2017-02-02 17:18:42 +0000
3023+++ miral/window_management_trace.h 2017-03-03 10:23:15 +0000
3024@@ -66,6 +66,7 @@
3025 virtual void focus_next_application() override;
3026
3027 virtual void focus_next_within_application() override;
3028+ virtual void focus_prev_within_application() override;
3029
3030 virtual void raise_tree(Window const& root) override;
3031
3032@@ -90,6 +91,23 @@
3033
3034 auto confirm_inherited_move(WindowInfo const& window_info, Displacement movement) -> Rectangle override;
3035
3036+ auto create_workspace() -> std::shared_ptr<Workspace> override;
3037+
3038+ void add_tree_to_workspace(Window const& window, std::shared_ptr<Workspace> const& workspace) override;
3039+
3040+ void remove_tree_from_workspace(Window const& window, std::shared_ptr<Workspace> const& workspace) override;
3041+
3042+ void move_workspace_content_to_workspace(
3043+ std::shared_ptr<Workspace> const& to_workspace,
3044+ std::shared_ptr<Workspace> const& from_workspace) override;
3045+
3046+ void for_each_workspace_containing(
3047+ Window const& window,
3048+ std::function<void(std::shared_ptr<Workspace> const& workspace)> const& callback) override;
3049+
3050+ void for_each_window_in_workspace(
3051+ std::shared_ptr<Workspace> const& workspace, std::function<void(Window const&)> const& callback) override;
3052+
3053 public:
3054 virtual void advise_begin() override;
3055
3056
3057=== modified file 'miral/window_manager_tools.cpp'
3058--- miral/window_manager_tools.cpp 2017-02-02 17:18:42 +0000
3059+++ miral/window_manager_tools.cpp 2017-03-03 10:23:15 +0000
3060@@ -71,6 +71,9 @@
3061 void miral::WindowManagerTools::focus_next_within_application()
3062 { tools->focus_next_within_application(); }
3063
3064+void miral::WindowManagerTools::focus_prev_within_application()
3065+{ tools->focus_prev_within_application(); }
3066+
3067 auto miral::WindowManagerTools::window_at(mir::geometry::Point cursor) const -> Window
3068 { return tools->window_at(cursor); }
3069
3070@@ -98,3 +101,31 @@
3071 void miral::WindowManagerTools::place_and_size_for_state(
3072 WindowSpecification& modifications, WindowInfo const& window_info) const
3073 { tools->place_and_size_for_state(modifications, window_info); }
3074+
3075+auto miral::WindowManagerTools::create_workspace() -> std::shared_ptr<miral::Workspace>
3076+{ return tools->create_workspace(); }
3077+
3078+void miral::WindowManagerTools::add_tree_to_workspace(
3079+ miral::Window const& window,
3080+ std::shared_ptr<miral::Workspace> const& workspace)
3081+{ tools->add_tree_to_workspace(window, workspace); }
3082+
3083+void miral::WindowManagerTools::remove_tree_from_workspace(
3084+ miral::Window const& window,
3085+ std::shared_ptr<miral::Workspace> const& workspace)
3086+{ tools->remove_tree_from_workspace(window, workspace); }
3087+
3088+void miral::WindowManagerTools::move_workspace_content_to_workspace(
3089+ std::shared_ptr<Workspace> const& to_workspace,
3090+ std::shared_ptr<Workspace> const& from_workspace)
3091+{ tools->move_workspace_content_to_workspace(to_workspace, from_workspace); }
3092+
3093+void miral::WindowManagerTools::for_each_workspace_containing(
3094+ miral::Window const& window,
3095+ std::function<void(std::shared_ptr<miral::Workspace> const&)> const& callback)
3096+{ tools->for_each_workspace_containing(window, callback); }
3097+
3098+void miral::WindowManagerTools::for_each_window_in_workspace(
3099+ std::shared_ptr<miral::Workspace> const& workspace,
3100+ std::function<void(miral::Window const&)> const& callback)
3101+{ tools->for_each_window_in_workspace(workspace, callback); }
3102
3103=== modified file 'miral/window_manager_tools_implementation.h'
3104--- miral/window_manager_tools_implementation.h 2017-02-02 17:18:42 +0000
3105+++ miral/window_manager_tools_implementation.h 2017-03-03 10:23:15 +0000
3106@@ -35,6 +35,7 @@
3107 struct WindowInfo;
3108 struct ApplicationInfo;
3109 class WindowSpecification;
3110+class Workspace;
3111
3112 // The interface through which the policy instructs the controller.
3113 class WindowManagerToolsImplementation
3114@@ -61,6 +62,7 @@
3115 virtual void drag_window(Window const& window, mir::geometry::Displacement& movement) = 0;
3116 virtual void focus_next_application() = 0;
3117 virtual void focus_next_within_application() = 0;
3118+ virtual void focus_prev_within_application() = 0;
3119 virtual auto window_at(mir::geometry::Point cursor) const -> Window = 0;
3120 virtual auto active_display() -> mir::geometry::Rectangle const = 0;
3121 virtual void raise_tree(Window const& root) = 0;
3122@@ -69,6 +71,19 @@
3123 virtual auto id_for_window(Window const& window) const -> std::string = 0;
3124 virtual void place_and_size_for_state(WindowSpecification& modifications, WindowInfo const& window_info) const= 0;
3125
3126+ virtual auto create_workspace() -> std::shared_ptr<Workspace> = 0;
3127+ virtual void add_tree_to_workspace(Window const& window, std::shared_ptr<Workspace> const& workspace) = 0;
3128+ virtual void remove_tree_from_workspace(Window const& window, std::shared_ptr<Workspace> const& workspace) = 0;
3129+ virtual void move_workspace_content_to_workspace(
3130+ std::shared_ptr<Workspace> const& to_workspace,
3131+ std::shared_ptr<Workspace> const& from_workspace) = 0;
3132+ virtual void for_each_workspace_containing(
3133+ Window const& window,
3134+ std::function<void(std::shared_ptr<Workspace> const& workspace)> const& callback) = 0;
3135+ virtual void for_each_window_in_workspace(
3136+ std::shared_ptr<Workspace> const& workspace,
3137+ std::function<void(Window const& window)> const& callback) = 0;
3138+
3139 /** @} */
3140
3141 /** @name Multi-thread support
3142
3143=== added file 'miral/workspace_policy.cpp'
3144--- miral/workspace_policy.cpp 1970-01-01 00:00:00 +0000
3145+++ miral/workspace_policy.cpp 2017-03-03 10:23:15 +0000
3146@@ -0,0 +1,29 @@
3147+/*
3148+ * Copyright © 2017 Canonical Ltd.
3149+ *
3150+ * This program is free software: you can redistribute it and/or modify it
3151+ * under the terms of the GNU General Public License version 3,
3152+ * as published by the Free Software Foundation.
3153+ *
3154+ * This program is distributed in the hope that it will be useful,
3155+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3156+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3157+ * GNU General Public License for more details.
3158+ *
3159+ * You should have received a copy of the GNU General Public License
3160+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3161+ *
3162+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
3163+ */
3164+
3165+#include <miral/workspace_policy.h>
3166+
3167+void miral::WorkspacePolicy::advise_adding_to_workspace(
3168+ std::shared_ptr<Workspace> const& , std::vector<Window> const&)
3169+{
3170+}
3171+
3172+void miral::WorkspacePolicy::advise_removing_from_workspace(
3173+ std::shared_ptr<Workspace> const&, std::vector<Window> const&)
3174+{
3175+}
3176
3177=== modified file 'scripts/process_doxygen_xml.py'
3178--- scripts/process_doxygen_xml.py 2017-02-15 12:06:25 +0000
3179+++ scripts/process_doxygen_xml.py 2017-03-03 10:23:15 +0000
3180@@ -443,10 +443,20 @@
3181
3182 # miral::StartupInternalClient::StartupInternalClient*;
3183 _ZN5miral21StartupInternalClientC?ENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESt8functionIFvN3mir6client10ConnectionEEES7_IFvSt8weak_ptrINS8_5scene7SessionEEEE;
3184+ extern "C++" {
3185+ miral::WindowInfo::shell_chrome*;
3186+ miral::WindowManagerTools::drag_window*;
3187+ typeinfo?for?miral::ApplicationAuthorizer1;
3188+ vtable?for?miral::ApplicationAuthorizer1;
3189+ };
3190+} MIRAL_1.1;
3191+
3192+MIRAL_1.3 {
3193+global:
3194 extern "C++" {'''
3195
3196 END_NEW_STANZA = ''' };
3197-} MIRAL_1.1;'''
3198+} MIRAL_1.2;'''
3199
3200 def _print_report():
3201 print OLD_STANZAS
3202
3203=== modified file 'test/CMakeLists.txt'
3204--- test/CMakeLists.txt 2017-02-10 16:48:00 +0000
3205+++ test/CMakeLists.txt 2017-03-03 10:23:15 +0000
3206@@ -56,7 +56,7 @@
3207 display_reconfiguration.cpp
3208 active_window.cpp
3209 raise_tree.cpp
3210-)
3211+ workspaces.cpp)
3212
3213 target_link_libraries(miral-test
3214 ${MIRTEST_LDFLAGS}
3215
3216=== modified file 'test/active_window.cpp'
3217--- test/active_window.cpp 2017-02-14 11:49:59 +0000
3218+++ test/active_window.cpp 2017-03-03 10:23:15 +0000
3219@@ -34,6 +34,12 @@
3220 using namespace std::chrono_literals;
3221 using miral::WindowManagerTools;
3222
3223+#if MIR_CLIENT_VERSION < MIR_VERSION_NUMBER(3, 5, 0)
3224+auto const mir_window_set_state = mir_surface_set_state;
3225+auto const mir_window_event_get_attribute = mir_surface_event_get_attribute;
3226+auto const mir_event_get_window_event = mir_event_get_surface_event;
3227+#endif
3228+
3229 namespace
3230 {
3231 class FocusChangeSync
3232@@ -48,13 +54,8 @@
3233
3234 static void raise_signal_on_focus_change(MirWindow* /*surface*/, MirEvent const* event, void* context)
3235 {
3236-#if MIR_CLIENT_VERSION < MIR_VERSION_NUMBER(3, 5, 0)
3237- if (mir_event_get_type(event) == mir_event_type_surface &&
3238- mir_surface_event_get_attribute(mir_event_get_surface_event(event)) == mir_surface_attrib_focus)
3239-#else
3240 if (mir_event_get_type(event) == mir_event_type_window &&
3241 mir_window_event_get_attribute(mir_event_get_window_event(event)) == mir_window_attrib_focus)
3242-#endif
3243 {
3244 ((FocusChangeSync*)context)->signal.raise();
3245 }
3246@@ -160,11 +161,7 @@
3247 auto const connection = connect_client(test_name);
3248 auto const surface = create_surface(connection, test_name, sync1);
3249
3250-#if MIR_CLIENT_VERSION <= MIR_VERSION_NUMBER(3, 4, 0)
3251- sync1.exec([&]{ mir_surface_set_state(surface, mir_surface_state_hidden); });
3252-#else
3253 sync1.exec([&]{ mir_window_set_state(surface, mir_window_state_hidden); });
3254-#endif
3255
3256 EXPECT_TRUE(sync1.signal_raised());
3257 assert_no_active_window();
3258@@ -175,15 +172,11 @@
3259 char const* const test_name = __PRETTY_FUNCTION__;
3260 auto const connection = connect_client(test_name);
3261 auto const surface = create_surface(connection, test_name, sync1);
3262-#if MIR_CLIENT_VERSION <= MIR_VERSION_NUMBER(3, 4, 0)
3263- sync1.exec([&]{ mir_surface_set_state(surface, mir_surface_state_hidden); });
3264
3265- sync1.exec([&]{ mir_surface_set_state(surface, mir_surface_state_restored); });
3266-#else
3267 sync1.exec([&]{ mir_window_set_state(surface, mir_window_state_hidden); });
3268
3269 sync1.exec([&]{ mir_window_set_state(surface, mir_window_state_restored); });
3270-#endif
3271+
3272 EXPECT_TRUE(sync1.signal_raised());
3273
3274 assert_active_window_is(test_name);
3275@@ -208,11 +201,7 @@
3276 auto const first_surface = create_surface(connection, test_name, sync1);
3277 auto const surface = create_surface(connection, another_name, sync2);
3278
3279-#if MIR_CLIENT_VERSION <= MIR_VERSION_NUMBER(3, 4, 0)
3280- sync2.exec([&]{ mir_surface_set_state(surface, mir_surface_state_hidden); });
3281-#else
3282 sync2.exec([&]{ mir_window_set_state(surface, mir_window_state_hidden); });
3283-#endif
3284
3285 EXPECT_TRUE(sync2.signal_raised());
3286 assert_active_window_is(test_name);
3287@@ -226,17 +215,11 @@
3288 auto const first_surface = create_surface(connection, test_name, sync1);
3289 auto const surface = create_surface(connection, another_name, sync2);
3290
3291-#if MIR_CLIENT_VERSION <= MIR_VERSION_NUMBER(3, 4, 0)
3292- sync1.exec([&]{ mir_surface_set_state(surface, mir_surface_state_hidden); });
3293-
3294- // Expect this to timeout
3295- sync2.exec([&]{ mir_surface_set_state(surface, mir_surface_state_restored); });
3296-#else
3297 sync1.exec([&]{ mir_window_set_state(surface, mir_window_state_hidden); });
3298
3299 // Expect this to timeout
3300 sync2.exec([&]{ mir_window_set_state(surface, mir_window_state_restored); });
3301-#endif
3302+
3303 EXPECT_THAT(sync2.signal_raised(), Eq(false));
3304 assert_active_window_is(test_name);
3305 }
3306
3307=== modified file 'test/mru_window_list.cpp'
3308--- test/mru_window_list.cpp 2016-08-05 08:53:45 +0000
3309+++ test/mru_window_list.cpp 2017-03-03 10:23:15 +0000
3310@@ -24,17 +24,23 @@
3311 #include <gtest/gtest.h>
3312 #include <gmock/gmock.h>
3313
3314-using StubSurface = mir::test::doubles::StubSurface;
3315 using namespace testing;
3316
3317 namespace
3318 {
3319+struct StubSurface : mir::test::doubles::StubSurface
3320+{
3321+ bool visible() const override { return visible_; }
3322+
3323+ bool visible_ = true;
3324+};
3325+
3326 struct StubSession : mir::test::doubles::StubSession
3327 {
3328 StubSession(int number_of_surfaces)
3329 {
3330 for (auto i = 0; i != number_of_surfaces; ++i)
3331- surfaces.push_back(std::make_shared<mir::test::doubles::StubSurface>());
3332+ surfaces.push_back(std::make_shared<StubSurface>());
3333 }
3334
3335 std::shared_ptr<mir::scene::Surface> surface(mir::frontend::SurfaceId surface) const override
3336@@ -53,13 +59,26 @@
3337
3338 struct MRUWindowList : testing::Test
3339 {
3340+ static auto const window_a_id = 0;
3341+ static auto const window_b_id = 1;
3342+
3343 miral::MRUWindowList mru_list;
3344
3345 std::shared_ptr<StubSession> const stub_session{std::make_shared<StubSession>(3)};
3346 miral::Application app{stub_session};
3347- miral::Window window_a{app, stub_session->surface(mir::frontend::SurfaceId{0})};
3348- miral::Window window_b{app, stub_session->surface(mir::frontend::SurfaceId{1})};
3349+ miral::Window window_a{app, stub_session->surface(mir::frontend::SurfaceId{window_a_id})};
3350+ miral::Window window_b{app, stub_session->surface(mir::frontend::SurfaceId{window_b_id})};
3351 miral::Window window_c{app, stub_session->surface(mir::frontend::SurfaceId{2})};
3352+
3353+ void hide_window(int window_id)
3354+ {
3355+ stub_session->surfaces[window_id]->visible_ = false;
3356+ }
3357+
3358+ void show_window(int window_id)
3359+ {
3360+ stub_session->surfaces[window_id]->visible_ = true;
3361+ }
3362 };
3363
3364 TEST_F(MRUWindowList, when_created_is_empty)
3365@@ -137,3 +156,38 @@
3366
3367 EXPECT_THAT(count, Eq(1));
3368 }
3369+
3370+TEST_F(MRUWindowList, a_hidden_window_is_not_enumerated)
3371+{
3372+ mru_list.push(window_a);
3373+ mru_list.push(window_b);
3374+ mru_list.push(window_c);
3375+
3376+ int count{0};
3377+
3378+ hide_window(window_a_id);
3379+ mru_list.enumerate([&](miral::Window& window)
3380+ { if (window == window_a) ++count; return true; });
3381+
3382+ EXPECT_THAT(count, Eq(0));
3383+}
3384+
3385+TEST_F(MRUWindowList, hiding_then_showing_windows_retains_order)
3386+{
3387+ mru_list.push(window_a);
3388+ mru_list.push(window_b);
3389+ mru_list.push(window_c);
3390+
3391+ hide_window(window_a_id);
3392+ hide_window(window_b_id);
3393+ show_window(window_a_id);
3394+ show_window(window_b_id);
3395+
3396+ std::vector<miral::Window> as_enumerated;
3397+
3398+ mru_list.enumerate([&](miral::Window& window)
3399+ { as_enumerated.push_back(window); return true; });
3400+
3401+ EXPECT_THAT(as_enumerated, ElementsAre(window_c, window_b, window_a));
3402+}
3403+
3404
3405=== modified file 'test/test_server.cpp'
3406--- test/test_server.cpp 2017-02-14 11:49:59 +0000
3407+++ test/test_server.cpp 2017-03-03 10:23:15 +0000
3408@@ -19,7 +19,6 @@
3409 #include "test_server.h"
3410 #include "../miral/basic_window_manager.h"
3411
3412-#include <miral/canonical_window_manager.h>
3413 #include <miral/set_window_managment_policy.h>
3414
3415 #include <mir_test_framework/executable_path.h>
3416@@ -48,19 +47,13 @@
3417 char const* dummy_args[2] = { "TestServer", nullptr };
3418 }
3419
3420-struct miral::TestServer::TestWindowManagerPolicy : CanonicalWindowManagerPolicy
3421+miral::TestServer::TestWindowManagerPolicy::TestWindowManagerPolicy(
3422+ WindowManagerTools const& tools, TestServer& test_fixture) :
3423+ CanonicalWindowManagerPolicy{tools}
3424 {
3425- TestWindowManagerPolicy(WindowManagerTools const& tools, TestServer& test_fixture) :
3426- CanonicalWindowManagerPolicy{tools}
3427- {
3428- test_fixture.tools = tools;
3429- test_fixture.policy = this;
3430- }
3431-
3432- bool handle_keyboard_event(MirKeyboardEvent const*) override { return false; }
3433- bool handle_pointer_event(MirPointerEvent const*) override { return false; }
3434- bool handle_touch_event(MirTouchEvent const*) override { return false; }
3435-};
3436+ test_fixture.tools = tools;
3437+ test_fixture.policy = this;
3438+}
3439
3440 miral::TestServer::TestServer() :
3441 runner{1, dummy_args}
3442@@ -70,6 +63,12 @@
3443 add_to_environment("MIR_SERVER_NO_FILE", "on");
3444 }
3445
3446+auto miral::TestServer::build_window_manager_policy(WindowManagerTools const& tools)
3447+-> std::unique_ptr<TestWindowManagerPolicy>
3448+{
3449+ return std::make_unique<TestWindowManagerPolicy>(tools, *this);
3450+}
3451+
3452 void miral::TestServer::SetUp()
3453 {
3454 #if MIR_SERVER_VERSION < MIR_VERSION_NUMBER(0, 25, 0)
3455@@ -114,7 +113,7 @@
3456
3457 auto builder = [this](WindowManagerTools const& tools) -> std::unique_ptr<miral::WindowManagementPolicy>
3458 {
3459- return std::make_unique<TestWindowManagerPolicy>(tools, *this);
3460+ return build_window_manager_policy(tools);
3461 };
3462
3463 auto wm = std::make_shared<miral::BasicWindowManager>(focus_controller, display_layout, persistent_surface_store, builder);
3464
3465=== modified file 'test/test_server.h'
3466--- test/test_server.h 2017-02-14 11:49:59 +0000
3467+++ test/test_server.h 2017-03-03 10:23:15 +0000
3468@@ -21,6 +21,7 @@
3469
3470 #include <mir/client/connection.h>
3471
3472+#include <miral/canonical_window_manager.h>
3473 #include <miral/runner.h>
3474 #include <miral/window_manager_tools.h>
3475
3476@@ -63,9 +64,10 @@
3477 void invoke_tools(std::function<void(WindowManagerTools& tools)> const& f);
3478 void invoke_window_manager(std::function<void(mir::shell::WindowManager& wm)> const& f);
3479
3480-private:
3481 struct TestWindowManagerPolicy;
3482+ virtual auto build_window_manager_policy(WindowManagerTools const& tools) -> std::unique_ptr<TestWindowManagerPolicy>;
3483
3484+private:
3485 WindowManagerTools tools{nullptr};
3486 WindowManagementPolicy* policy{nullptr};
3487 std::weak_ptr<mir::shell::WindowManager> window_manager;
3488@@ -74,6 +76,16 @@
3489 std::condition_variable started;
3490 mir::Server* server_running{nullptr};
3491 };
3492+
3493+struct TestServer::TestWindowManagerPolicy : CanonicalWindowManagerPolicy
3494+{
3495+ TestWindowManagerPolicy(WindowManagerTools const& tools, TestServer& test_fixture);
3496+
3497+ bool handle_keyboard_event(MirKeyboardEvent const*) override { return false; }
3498+ bool handle_pointer_event(MirPointerEvent const*) override { return false; }
3499+ bool handle_touch_event(MirTouchEvent const*) override { return false; }
3500+};
3501+
3502 }
3503
3504 #endif //MIRAL_TEST_SERVER_H
3505
3506=== modified file 'test/window_properties.cpp'
3507--- test/window_properties.cpp 2017-02-14 11:49:59 +0000
3508+++ test/window_properties.cpp 2017-03-03 10:23:15 +0000
3509@@ -54,7 +54,28 @@
3510 }
3511
3512 Connection client_connection;
3513+
3514+ std::unique_ptr<TestWindowManagerPolicy> build_window_manager_policy(WindowManagerTools const& tools) override;
3515+
3516+ mir::test::Signal window_ready;
3517 };
3518+
3519+auto WindowProperties::build_window_manager_policy(WindowManagerTools const& tools)
3520+-> std::unique_ptr<miral::TestServer::TestWindowManagerPolicy>
3521+{
3522+ struct MockWindowManagerPolicy : miral::TestServer::TestWindowManagerPolicy
3523+ {
3524+ using miral::TestServer::TestWindowManagerPolicy::TestWindowManagerPolicy;
3525+ MOCK_METHOD1(advise_focus_gained, void (miral::WindowInfo const& window_info));
3526+ };
3527+
3528+ auto result = std::make_unique<MockWindowManagerPolicy>(tools, *this);
3529+
3530+ ON_CALL(*result, advise_focus_gained(_))
3531+ .WillByDefault(InvokeWithoutArgs([this] { window_ready.raise(); }));
3532+
3533+ return std::move(result);
3534+}
3535 }
3536
3537 TEST_F(WindowProperties, on_creation_default_shell_chrome_is_normal)
3538@@ -65,6 +86,7 @@
3539 .create_window();
3540
3541 mir_buffer_stream_swap_buffers_sync(mir_window_get_buffer_stream(window));
3542+ ASSERT_TRUE(window_ready.wait_for(400ms));
3543
3544 invoke_tools([&, this](WindowManagerTools& tools)
3545 {
3546@@ -81,6 +103,7 @@
3547 .create_window();
3548
3549 mir_buffer_stream_swap_buffers_sync(mir_window_get_buffer_stream(window));
3550+ ASSERT_TRUE(window_ready.wait_for(400ms));
3551
3552 invoke_tools([&, this](WindowManagerTools& tools)
3553 {
3554@@ -100,6 +123,7 @@
3555 .apply_to(window);
3556
3557 mir_buffer_stream_swap_buffers_sync(mir_window_get_buffer_stream(window));
3558+ ASSERT_TRUE(window_ready.wait_for(400ms));
3559
3560 invoke_tools([&, this](WindowManagerTools& tools)
3561 {
3562@@ -120,6 +144,7 @@
3563 .apply_to(window);
3564
3565 mir_buffer_stream_swap_buffers_sync(mir_window_get_buffer_stream(window));
3566+ ASSERT_TRUE(window_ready.wait_for(400ms));
3567
3568 invoke_tools([&, this](WindowManagerTools& tools)
3569 {
3570
3571=== added file 'test/workspaces.cpp'
3572--- test/workspaces.cpp 1970-01-01 00:00:00 +0000
3573+++ test/workspaces.cpp 2017-03-03 10:23:15 +0000
3574@@ -0,0 +1,669 @@
3575+/*
3576+ * Copyright © 2017 Canonical Ltd.
3577+ *
3578+ * This program is free software: you can redistribute it and/or modify it
3579+ * under the terms of the GNU General Public License version 3,
3580+ * as published by the Free Software Foundation.
3581+ *
3582+ * This program is distributed in the hope that it will be useful,
3583+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3584+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3585+ * GNU General Public License for more details.
3586+ *
3587+ * You should have received a copy of the GNU General Public License
3588+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3589+ *
3590+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
3591+ */
3592+
3593+#include <miral/workspace_policy.h>
3594+#include <miral/window_manager_tools.h>
3595+
3596+#include <mir/client/window.h>
3597+#include <mir/client/window_spec.h>
3598+#include <mir_toolkit/mir_buffer_stream.h>
3599+
3600+#include "test_server.h"
3601+
3602+#include <gmock/gmock.h>
3603+#include <mir/test/signal.h>
3604+
3605+
3606+using namespace testing;
3607+using namespace mir::client;
3608+using namespace std::chrono_literals;
3609+using miral::WindowManagerTools;
3610+
3611+namespace
3612+{
3613+#if MIR_CLIENT_VERSION <= MIR_VERSION_NUMBER(3, 4, 0)
3614+auto const mir_window_get_buffer_stream = mir_surface_get_buffer_stream;
3615+auto const mir_window_set_state = mir_surface_set_state;
3616+#endif
3617+
3618+std::string const top_level{"top level"};
3619+std::string const dialog{"dialog"};
3620+std::string const tip{"tip"};
3621+std::string const a_window{"a window"};
3622+std::string const another_window{"another window"};
3623+
3624+struct Workspaces;
3625+
3626+struct WorkspacesWindowManagerPolicy : miral::TestServer::TestWindowManagerPolicy, miral::WorkspacePolicy
3627+{
3628+ WorkspacesWindowManagerPolicy(WindowManagerTools const& tools, Workspaces& test_fixture);
3629+ ~WorkspacesWindowManagerPolicy();
3630+
3631+ void advise_new_window(miral::WindowInfo const& window_info) override;
3632+
3633+ MOCK_METHOD2(advise_adding_to_workspace,
3634+ void(std::shared_ptr<miral::Workspace> const&, std::vector<miral::Window> const&));
3635+
3636+ MOCK_METHOD2(advise_removing_from_workspace,
3637+ void(std::shared_ptr<miral::Workspace> const&, std::vector<miral::Window> const&));
3638+
3639+ MOCK_METHOD1(advise_focus_gained, void(miral::WindowInfo const&));
3640+
3641+ Workspaces& test_fixture;
3642+};
3643+
3644+struct Workspaces : public miral::TestServer
3645+{
3646+ auto create_window(std::string const& name) -> Window
3647+ {
3648+ auto const window = WindowSpec::for_normal_window(client_connection, 50, 50, mir_pixel_format_argb_8888)
3649+ .set_buffer_usage(mir_buffer_usage_software)
3650+ .set_name(name.c_str())
3651+ .create_window();
3652+
3653+ client_windows[name] = window;
3654+ mir_buffer_stream_swap_buffers_sync(mir_window_get_buffer_stream(window));
3655+
3656+ return window;
3657+ }
3658+
3659+ auto create_tip(std::string const& name, Window const& parent) -> Window
3660+ {
3661+ MirRectangle aux_rect{10, 10, 10, 10};
3662+ auto const window = WindowSpec::for_tip(client_connection, 50, 50, mir_pixel_format_argb_8888, parent,
3663+ &aux_rect, mir_edge_attachment_any)
3664+ .set_buffer_usage(mir_buffer_usage_software)
3665+ .set_name(name.c_str())
3666+ .create_window();
3667+
3668+ client_windows[name] = window;
3669+ mir_buffer_stream_swap_buffers_sync(mir_window_get_buffer_stream(window));
3670+
3671+ return window;
3672+ }
3673+
3674+ auto create_dialog(std::string const& name, Window const& parent) -> Window
3675+ {
3676+ auto const window = WindowSpec::for_dialog(client_connection, 50, 50, mir_pixel_format_argb_8888, parent)
3677+ .set_buffer_usage(mir_buffer_usage_software)
3678+ .set_name(name.c_str())
3679+ .create_window();
3680+
3681+ client_windows[name] = window;
3682+ mir_buffer_stream_swap_buffers_sync(mir_window_get_buffer_stream(window));
3683+
3684+ return window;
3685+ }
3686+
3687+ auto create_workspace() -> std::shared_ptr<miral::Workspace>
3688+ {
3689+ std::shared_ptr<miral::Workspace> result;
3690+
3691+ invoke_tools([&](WindowManagerTools& tools)
3692+ { result = tools.create_workspace(); });
3693+
3694+ return result;
3695+ }
3696+
3697+ void SetUp() override
3698+ {
3699+ miral::TestServer::SetUp();
3700+ EXPECT_CALL(policy(), advise_adding_to_workspace(_, _)).Times(AnyNumber());
3701+ EXPECT_CALL(policy(), advise_removing_from_workspace(_, _)).Times(AnyNumber());
3702+ EXPECT_CALL(policy(), advise_focus_gained(_)).Times(AnyNumber());
3703+
3704+ client_connection = connect_client("Workspaces");
3705+ create_window(top_level);
3706+ create_dialog(dialog, client_windows[top_level]);
3707+ create_tip(tip, client_windows[dialog]);
3708+
3709+ EXPECT_THAT(client_windows.size(), Eq(3u));
3710+ EXPECT_THAT(server_windows.size(), Eq(3u));
3711+ }
3712+
3713+ void TearDown() override
3714+ {
3715+ client_windows.clear();
3716+ client_connection.reset();
3717+ miral::TestServer::TearDown();
3718+ }
3719+
3720+ Connection client_connection;
3721+
3722+ auto server_window(std::string const& key) -> miral::Window
3723+ {
3724+ std::lock_guard<decltype(mutex)> lock{mutex};
3725+ return server_windows[key];
3726+ }
3727+
3728+ auto client_window(std::string const& key) -> Window&
3729+ {
3730+ return client_windows[key];
3731+ }
3732+
3733+ auto windows_in_workspace(std::shared_ptr<miral::Workspace> const& workspace) -> std::vector<miral::Window>
3734+ {
3735+ std::vector<miral::Window> result;
3736+
3737+ auto enumerate = [&result](miral::Window const& window)
3738+ {
3739+ result.push_back(window);
3740+ };
3741+
3742+ invoke_tools([&](WindowManagerTools& tools)
3743+ { tools.for_each_window_in_workspace(workspace, enumerate); });
3744+
3745+ return result;
3746+ }
3747+
3748+ auto workspaces_containing_window(miral::Window const& window) -> std::vector<std::shared_ptr<miral::Workspace>>
3749+ {
3750+ std::vector<std::shared_ptr<miral::Workspace>> result;
3751+
3752+ auto enumerate = [&result](std::shared_ptr<miral::Workspace> const& workspace)
3753+ {
3754+ result.push_back(workspace);
3755+ };
3756+
3757+ invoke_tools([&](WindowManagerTools& tools)
3758+ { tools.for_each_workspace_containing(window, enumerate); });
3759+
3760+ return result;
3761+ }
3762+
3763+ auto policy() -> WorkspacesWindowManagerPolicy&
3764+ {
3765+ if (!the_policy) throw std::logic_error("the_policy isn't valid");
3766+ return *the_policy;
3767+ }
3768+
3769+private:
3770+ std::mutex mutable mutex;
3771+ std::map<std::string, Window> client_windows;
3772+ std::map<std::string, miral::Window> server_windows;
3773+ WorkspacesWindowManagerPolicy* the_policy{nullptr};
3774+
3775+ friend struct WorkspacesWindowManagerPolicy;
3776+
3777+ auto build_window_manager_policy(WindowManagerTools const& tools)
3778+ -> std::unique_ptr<TestWindowManagerPolicy> override
3779+ {
3780+ return std::make_unique<WorkspacesWindowManagerPolicy>(tools, *this);
3781+ }
3782+};
3783+
3784+WorkspacesWindowManagerPolicy::WorkspacesWindowManagerPolicy(WindowManagerTools const& tools, Workspaces& test_fixture) :
3785+TestWindowManagerPolicy(tools, test_fixture), test_fixture{test_fixture}
3786+{
3787+ test_fixture.the_policy = this;
3788+}
3789+
3790+WorkspacesWindowManagerPolicy::~WorkspacesWindowManagerPolicy()
3791+{
3792+ test_fixture.the_policy = nullptr;
3793+}
3794+
3795+
3796+void WorkspacesWindowManagerPolicy::advise_new_window(miral::WindowInfo const& window_info)
3797+{
3798+ miral::TestServer::TestWindowManagerPolicy::advise_new_window(window_info);
3799+
3800+ std::lock_guard<decltype(test_fixture.mutex)> lock{test_fixture.mutex};
3801+ test_fixture.server_windows[window_info.name()] = window_info.window();
3802+}
3803+}
3804+
3805+TEST_F(Workspaces, before_a_tree_is_added_to_workspace_it_is_empty)
3806+{
3807+ auto const workspace = create_workspace();
3808+
3809+ EXPECT_THAT(windows_in_workspace(workspace).size(), Eq(0u));
3810+}
3811+
3812+TEST_F(Workspaces, when_a_tree_is_added_to_workspace_all_surfaces_in_tree_are_added)
3813+{
3814+ auto const workspace = create_workspace();
3815+ invoke_tools([&, this](WindowManagerTools& tools)
3816+ { tools.add_tree_to_workspace(server_window(dialog), workspace); });
3817+
3818+ EXPECT_THAT(windows_in_workspace(workspace).size(), Eq(3u));
3819+}
3820+
3821+TEST_F(Workspaces, when_a_tree_is_removed_from_workspace_all_surfaces_in_tree_are_removed)
3822+{
3823+ auto const workspace = create_workspace();
3824+ invoke_tools([&, this](WindowManagerTools& tools)
3825+ { tools.add_tree_to_workspace(server_window(dialog), workspace); });
3826+
3827+ invoke_tools([&, this](WindowManagerTools& tools)
3828+ { tools.remove_tree_from_workspace(server_window(tip), workspace); });
3829+
3830+ EXPECT_THAT(windows_in_workspace(workspace).size(), Eq(0u));
3831+}
3832+
3833+TEST_F(Workspaces, given_a_tree_in_a_workspace_when_another_tree_is_added_and_removed_from_workspace_the_original_tree_remains)
3834+{
3835+ auto const workspace = create_workspace();
3836+ auto const original_tree = "original_tree";
3837+ auto const client_window = create_window(original_tree);
3838+ auto const original_window= server_window(original_tree);
3839+
3840+ invoke_tools([&, this](WindowManagerTools& tools)
3841+ { tools.add_tree_to_workspace(original_window, workspace); });
3842+
3843+ invoke_tools([&, this](WindowManagerTools& tools)
3844+ { tools.add_tree_to_workspace(server_window(top_level), workspace); });
3845+ invoke_tools([&, this](WindowManagerTools& tools)
3846+ { tools.remove_tree_from_workspace(server_window(top_level), workspace); });
3847+
3848+ EXPECT_THAT(windows_in_workspace(workspace), ElementsAre(original_window));
3849+}
3850+
3851+TEST_F(Workspaces, when_a_tree_is_added_to_a_workspace_all_surfaces_are_contained_in_the_workspace)
3852+{
3853+ auto const workspace = create_workspace();
3854+ invoke_tools([&, this](WindowManagerTools& tools)
3855+ { tools.add_tree_to_workspace(server_window(dialog), workspace); });
3856+
3857+ EXPECT_THAT(workspaces_containing_window(server_window(top_level)), ElementsAre(workspace));
3858+ EXPECT_THAT(workspaces_containing_window(server_window(dialog)), ElementsAre(workspace));
3859+ EXPECT_THAT(workspaces_containing_window(server_window(tip)), ElementsAre(workspace));
3860+}
3861+
3862+
3863+TEST_F(Workspaces, when_a_tree_is_added_to_a_workspaces_twice_surfaces_are_contained_in_one_workspace)
3864+{
3865+ auto const workspace = create_workspace();
3866+ invoke_tools([&, this](WindowManagerTools& tools)
3867+ {
3868+ tools.add_tree_to_workspace(server_window(dialog), workspace);
3869+ tools.add_tree_to_workspace(server_window(dialog), workspace);
3870+ });
3871+
3872+ EXPECT_THAT(workspaces_containing_window(server_window(top_level)), ElementsAre(workspace));
3873+ EXPECT_THAT(workspaces_containing_window(server_window(dialog)), ElementsAre(workspace));
3874+ EXPECT_THAT(workspaces_containing_window(server_window(tip)), ElementsAre(workspace));
3875+
3876+ EXPECT_THAT(workspaces_containing_window(server_window(top_level)).size(), Eq(1u));
3877+ EXPECT_THAT(workspaces_containing_window(server_window(dialog)).size(), Eq(1u));
3878+ EXPECT_THAT(workspaces_containing_window(server_window(tip)).size(), Eq(1u));
3879+}
3880+
3881+TEST_F(Workspaces, when_a_tree_is_added_to_two_workspaces_all_surfaces_are_contained_in_two_workspaces)
3882+{
3883+ auto const workspace1 = create_workspace();
3884+ auto const workspace2 = create_workspace();
3885+ invoke_tools([&, this](WindowManagerTools& tools)
3886+ {
3887+ tools.add_tree_to_workspace(server_window(dialog), workspace1);
3888+ tools.add_tree_to_workspace(server_window(dialog), workspace2);
3889+ });
3890+
3891+ EXPECT_THAT(workspaces_containing_window(server_window(top_level)).size(), Eq(2u));
3892+ EXPECT_THAT(workspaces_containing_window(server_window(dialog)).size(), Eq(2u));
3893+ EXPECT_THAT(workspaces_containing_window(server_window(tip)).size(), Eq(2u));
3894+}
3895+
3896+TEST_F(Workspaces, when_workspace_is_closed_surfaces_are_no_longer_contained_in_it)
3897+{
3898+ auto const workspace1 = create_workspace();
3899+ auto workspace2 = create_workspace();
3900+ invoke_tools([&, this](WindowManagerTools& tools)
3901+ {
3902+ tools.add_tree_to_workspace(server_window(dialog), workspace1);
3903+ tools.add_tree_to_workspace(server_window(dialog), workspace2);
3904+ });
3905+
3906+ workspace2.reset();
3907+
3908+ EXPECT_THAT(workspaces_containing_window(server_window(top_level)), ElementsAre(workspace1));
3909+ EXPECT_THAT(workspaces_containing_window(server_window(dialog)), ElementsAre(workspace1));
3910+ EXPECT_THAT(workspaces_containing_window(server_window(tip)), ElementsAre(workspace1));
3911+
3912+ EXPECT_THAT(workspaces_containing_window(server_window(top_level)).size(), Eq(1u));
3913+ EXPECT_THAT(workspaces_containing_window(server_window(dialog)).size(), Eq(1u));
3914+ EXPECT_THAT(workspaces_containing_window(server_window(tip)).size(), Eq(1u));
3915+}
3916+
3917+TEST_F(Workspaces, when_a_tree_is_added_to_a_workspace_the_policy_is_notified)
3918+{
3919+ auto const workspace = create_workspace();
3920+
3921+ EXPECT_CALL(policy(), advise_adding_to_workspace(workspace,
3922+ ElementsAre(server_window(top_level), server_window(dialog), server_window(tip))));
3923+
3924+ invoke_tools([&, this](WindowManagerTools& tools)
3925+ { tools.add_tree_to_workspace(server_window(dialog), workspace); });
3926+}
3927+
3928+TEST_F(Workspaces, when_a_tree_is_added_to_a_workspaces_twice_the_policy_is_notified_once)
3929+{
3930+ auto const workspace = create_workspace();
3931+
3932+ EXPECT_CALL(policy(), advise_adding_to_workspace(workspace,
3933+ ElementsAre(server_window(top_level), server_window(dialog), server_window(tip))));
3934+
3935+ invoke_tools([&, this](WindowManagerTools& tools)
3936+ {
3937+ tools.add_tree_to_workspace(server_window(dialog), workspace);
3938+ tools.add_tree_to_workspace(server_window(dialog), workspace);
3939+ });
3940+}
3941+
3942+TEST_F(Workspaces, when_a_tree_is_removed_from_a_workspace_the_policy_is_notified)
3943+{
3944+ auto const workspace = create_workspace();
3945+ invoke_tools([&, this](WindowManagerTools& tools)
3946+ { tools.add_tree_to_workspace(server_window(dialog), workspace); });
3947+
3948+ EXPECT_CALL(policy(), advise_removing_from_workspace(workspace,
3949+ ElementsAre(server_window(top_level), server_window(dialog), server_window(tip))));
3950+
3951+ invoke_tools([&, this](WindowManagerTools& tools)
3952+ { tools.remove_tree_from_workspace(server_window(tip), workspace); });
3953+}
3954+
3955+TEST_F(Workspaces, when_a_tree_is_removed_from_a_workspace_twice_the_policy_is_notified_once)
3956+{
3957+ auto const workspace = create_workspace();
3958+ invoke_tools([&, this](WindowManagerTools& tools)
3959+ { tools.add_tree_to_workspace(server_window(dialog), workspace); });
3960+
3961+ EXPECT_CALL(policy(), advise_removing_from_workspace(workspace,
3962+ ElementsAre(server_window(top_level), server_window(dialog), server_window(tip))));
3963+
3964+ invoke_tools([&, this](WindowManagerTools& tools)
3965+ {
3966+ tools.remove_tree_from_workspace(server_window(top_level), workspace);
3967+ tools.remove_tree_from_workspace(server_window(tip), workspace);
3968+ });
3969+}
3970+
3971+TEST_F(Workspaces, a_child_window_is_added_to_workspace_of_parent)
3972+{
3973+ auto const workspace = create_workspace();
3974+ invoke_tools([&, this](WindowManagerTools& tools)
3975+ { tools.add_tree_to_workspace(server_window(dialog), workspace); });
3976+
3977+ EXPECT_CALL(policy(), advise_adding_to_workspace(workspace, ElementsAre(_)));
3978+
3979+ create_dialog(a_window, client_window(top_level));
3980+}
3981+
3982+TEST_F(Workspaces, a_closing_window_is_removed_from_workspace)
3983+{
3984+ auto const workspace = create_workspace();
3985+ invoke_tools([&, this](WindowManagerTools& tools)
3986+ { tools.add_tree_to_workspace(server_window(dialog), workspace); });
3987+
3988+ create_dialog(a_window, client_window(dialog));
3989+
3990+ EXPECT_CALL(policy(), advise_removing_from_workspace(workspace, ElementsAre(server_window(a_window))));
3991+
3992+ client_window(a_window).reset();
3993+}
3994+
3995+TEST_F(Workspaces, when_a_window_in_a_workspace_closes_focus_remains_in_workspace)
3996+{
3997+ auto const workspace = create_workspace();
3998+
3999+ create_window(a_window);
4000+ create_window(another_window);
4001+
4002+ invoke_tools([&, this](WindowManagerTools& tools)
4003+ {
4004+ tools.add_tree_to_workspace(server_window(a_window), workspace);
4005+ tools.add_tree_to_workspace(server_window(another_window), workspace);
4006+
4007+ tools.select_active_window(server_window(dialog));
4008+ tools.select_active_window(server_window(a_window));
4009+ });
4010+
4011+ client_window(a_window).reset();
4012+
4013+ invoke_tools([&, this](WindowManagerTools& tools)
4014+ {
4015+ EXPECT_THAT(tools.active_window(), Eq(server_window(another_window)))
4016+ << "tools.active_window() . . . .: " << tools.info_for(tools.active_window()).name() << "\n"
4017+ << "server_window(another_window): " << tools.info_for(server_window(another_window)).name();
4018+ });
4019+}
4020+
4021+TEST_F(Workspaces, with_two_applications_when_a_window_in_a_workspace_closes_focus_remains_in_workspace)
4022+{
4023+ auto const workspace = create_workspace();
4024+
4025+ create_window(another_window);
4026+
4027+ {
4028+ auto const another_app = connect_client("another app");
4029+ auto const window = WindowSpec::for_normal_window(another_app, 50, 50, mir_pixel_format_argb_8888)
4030+ .set_buffer_usage(mir_buffer_usage_software)
4031+ .set_name(a_window.c_str())
4032+ .create_window();
4033+
4034+ mir_buffer_stream_swap_buffers_sync(mir_window_get_buffer_stream(window));
4035+
4036+ invoke_tools([&, this](WindowManagerTools& tools)
4037+ {
4038+ tools.add_tree_to_workspace(server_window(top_level), workspace);
4039+ tools.add_tree_to_workspace(server_window(a_window), workspace);
4040+ });
4041+ }
4042+
4043+ invoke_tools([&, this](WindowManagerTools& tools)
4044+ {
4045+ EXPECT_THAT(tools.active_window(), Eq(server_window(dialog)))
4046+ << "tools.active_window(): " << tools.info_for(tools.active_window()).name() << "\n"
4047+ << "server_window(dialog): " << tools.info_for(server_window(dialog)).name();
4048+ });
4049+}
4050+
4051+TEST_F(Workspaces, when_a_window_in_a_workspace_hides_focus_remains_in_workspace)
4052+{
4053+ auto const workspace = create_workspace();
4054+
4055+ create_window(a_window);
4056+ create_window(another_window);
4057+
4058+ invoke_tools([&, this](WindowManagerTools& tools)
4059+ {
4060+ tools.add_tree_to_workspace(server_window(a_window), workspace);
4061+ tools.add_tree_to_workspace(server_window(another_window), workspace);
4062+
4063+ tools.select_active_window(server_window(dialog));
4064+ tools.select_active_window(server_window(a_window));
4065+ });
4066+
4067+ mir::test::Signal focus_changed;
4068+ EXPECT_CALL(policy(), advise_focus_gained(_)).WillOnce(InvokeWithoutArgs([&]{ focus_changed.raise(); }));
4069+
4070+ mir_window_set_state(client_window(a_window), mir_window_state_hidden);
4071+
4072+ EXPECT_TRUE(focus_changed.wait_for(1s));
4073+
4074+ invoke_tools([&, this](WindowManagerTools& tools)
4075+ {
4076+ EXPECT_THAT(tools.active_window(), Eq(server_window(another_window)))
4077+ << "tools.active_window() . . . .: " << tools.info_for(tools.active_window()).name() << "\n"
4078+ << "server_window(another_window): " << tools.info_for(server_window(another_window)).name();
4079+ });
4080+}
4081+
4082+
4083+TEST_F(Workspaces, with_two_applications_when_a_window_in_a_workspace_hides_focus_remains_in_workspace)
4084+{
4085+ auto const workspace = create_workspace();
4086+
4087+ create_window(another_window);
4088+
4089+ auto const another_app = connect_client("another app");
4090+ auto const window = WindowSpec::for_normal_window(another_app, 50, 50, mir_pixel_format_argb_8888)
4091+ .set_buffer_usage(mir_buffer_usage_software)
4092+ .set_name(a_window.c_str())
4093+ .create_window();
4094+
4095+ mir_buffer_stream_swap_buffers_sync(mir_window_get_buffer_stream(window));
4096+
4097+ invoke_tools([&, this](WindowManagerTools& tools)
4098+ {
4099+ tools.add_tree_to_workspace(server_window(top_level), workspace);
4100+ tools.add_tree_to_workspace(server_window(a_window), workspace);
4101+ });
4102+
4103+
4104+ mir::test::Signal focus_changed;
4105+ EXPECT_CALL(policy(), advise_focus_gained(_)).WillOnce(InvokeWithoutArgs([&]{ focus_changed.raise(); }));
4106+
4107+ mir_window_set_state(window, mir_window_state_hidden);
4108+
4109+ EXPECT_TRUE(focus_changed.wait_for(1s));
4110+
4111+ invoke_tools([&, this](WindowManagerTools& tools)
4112+ {
4113+ EXPECT_THAT(tools.active_window(), Eq(server_window(dialog)))
4114+ << "tools.active_window(): " << tools.info_for(tools.active_window()).name() << "\n"
4115+ << "server_window(dialog): " << tools.info_for(server_window(dialog)).name();
4116+ });
4117+
4118+ Mock::VerifyAndClearExpectations(&policy()); // before shutdown
4119+}
4120+
4121+TEST_F(Workspaces, focus_next_within_application_keeps_focus_in_workspace)
4122+{
4123+ auto const workspace = create_workspace();
4124+
4125+ create_window(another_window);
4126+ create_window(a_window);
4127+
4128+ invoke_tools([&, this](WindowManagerTools& tools)
4129+ {
4130+ tools.add_tree_to_workspace(server_window(a_window), workspace);
4131+ tools.add_tree_to_workspace(server_window(dialog), workspace);
4132+
4133+ tools.focus_next_within_application();
4134+
4135+ EXPECT_THAT(tools.active_window(), Eq(server_window(dialog)))
4136+ << "tools.active_window(): " << tools.info_for(tools.active_window()).name() << "\n"
4137+ << "server_window(dialog): " << tools.info_for(server_window(dialog)).name();
4138+
4139+ tools.focus_next_within_application();
4140+
4141+ EXPECT_THAT(tools.active_window(), Eq(server_window(a_window)))
4142+ << "tools.active_window(). : " << tools.info_for(tools.active_window()).name() << "\n"
4143+ << "server_window(a_window): " << tools.info_for(server_window(a_window)).name();
4144+ });
4145+}
4146+
4147+TEST_F(Workspaces, focus_next_application_keeps_focus_in_workspace)
4148+{
4149+ auto const workspace = create_workspace();
4150+ create_window(another_window);
4151+
4152+ auto const another_app = connect_client("another app");
4153+ auto const window = WindowSpec::for_normal_window(another_app, 50, 50, mir_pixel_format_argb_8888)
4154+ .set_buffer_usage(mir_buffer_usage_software)
4155+ .set_name(a_window.c_str())
4156+ .create_window();
4157+
4158+ mir_buffer_stream_swap_buffers_sync(mir_window_get_buffer_stream(window));
4159+
4160+ invoke_tools([&, this](WindowManagerTools& tools)
4161+ {
4162+ tools.add_tree_to_workspace(server_window(top_level), workspace);
4163+ tools.add_tree_to_workspace(server_window(a_window), workspace);
4164+
4165+ tools.focus_next_application();
4166+
4167+ EXPECT_THAT(tools.active_window(), Eq(server_window(dialog)))
4168+ << "tools.active_window(): " << tools.info_for(tools.active_window()).name() << "\n"
4169+ << "server_window(dialog): " << tools.info_for(server_window(dialog)).name();
4170+
4171+ tools.focus_next_application();
4172+
4173+ EXPECT_THAT(tools.active_window(), Eq(server_window(a_window)))
4174+ << "tools.active_window(). : " << tools.info_for(tools.active_window()).name() << "\n"
4175+ << "server_window(a_window): " << tools.info_for(server_window(a_window)).name();
4176+ });
4177+}
4178+
4179+TEST_F(Workspaces, move_windows_from_one_workspace_to_another)
4180+{
4181+ auto const pre_workspace = create_workspace();
4182+ auto const from_workspace = create_workspace();
4183+ auto const to_workspace = create_workspace();
4184+ auto const post_workspace = create_workspace();
4185+
4186+ create_window(a_window);
4187+ create_window(another_window);
4188+
4189+ invoke_tools([&, this](WindowManagerTools& tools)
4190+ {
4191+ tools.add_tree_to_workspace(server_window(a_window), pre_workspace);
4192+ tools.add_tree_to_workspace(server_window(top_level), from_workspace);
4193+ tools.add_tree_to_workspace(server_window(another_window), post_workspace);
4194+
4195+ tools.move_workspace_content_to_workspace(to_workspace, from_workspace);
4196+ });
4197+
4198+ EXPECT_THAT(windows_in_workspace(from_workspace).size(), Eq(0u));
4199+
4200+ EXPECT_THAT(workspaces_containing_window(server_window(a_window)), ElementsAre(pre_workspace));
4201+ EXPECT_THAT(workspaces_containing_window(server_window(top_level)), ElementsAre(to_workspace));
4202+ EXPECT_THAT(workspaces_containing_window(server_window(dialog)), ElementsAre(to_workspace));
4203+ EXPECT_THAT(workspaces_containing_window(server_window(tip)), ElementsAre(to_workspace));
4204+ EXPECT_THAT(workspaces_containing_window(server_window(another_window)), ElementsAre(post_workspace));
4205+}
4206+
4207+TEST_F(Workspaces, when_moving_windows_from_one_workspace_to_another_windows_only_appear_once_in_target_workspace)
4208+{
4209+ auto const from_workspace = create_workspace();
4210+ auto const to_workspace = create_workspace();
4211+
4212+ create_window(a_window);
4213+ create_window(another_window);
4214+
4215+ invoke_tools([&, this](WindowManagerTools& tools)
4216+ {
4217+ tools.add_tree_to_workspace(server_window(a_window), from_workspace);
4218+ tools.add_tree_to_workspace(server_window(another_window), from_workspace);
4219+ tools.add_tree_to_workspace(server_window(a_window), to_workspace);
4220+
4221+ tools.move_workspace_content_to_workspace(to_workspace, from_workspace);
4222+ });
4223+
4224+ EXPECT_THAT(windows_in_workspace(to_workspace), ElementsAre(server_window(a_window), server_window(another_window)));
4225+}
4226+
4227+TEST_F(Workspaces, when_workspace_content_is_moved_the_policy_is_notified)
4228+{
4229+ auto const from_workspace = create_workspace();
4230+ auto const to_workspace = create_workspace();
4231+
4232+ EXPECT_CALL(policy(), advise_removing_from_workspace(from_workspace,
4233+ ElementsAre(server_window(top_level), server_window(dialog), server_window(tip))));
4234+
4235+ EXPECT_CALL(policy(), advise_adding_to_workspace(to_workspace,
4236+ ElementsAre(server_window(top_level), server_window(dialog), server_window(tip))));
4237+
4238+ invoke_tools([&, this](WindowManagerTools& tools)
4239+ {
4240+ tools.add_tree_to_workspace(server_window(dialog), from_workspace);
4241+ tools.move_workspace_content_to_workspace(to_workspace, from_workspace);
4242+ });
4243+}

Subscribers

People subscribed via source and target branches