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