Mir

Merge lp:~alan-griffiths/mir/tiling-window-mamagement into lp:mir

Proposed by Alan Griffiths on 2014-12-19
Status: Merged
Approved by: Daniel van Vugt on 2015-01-06
Approved revision: 2191
Merged at revision: 2198
Proposed branch: lp:~alan-griffiths/mir/tiling-window-mamagement
Merge into: lp:mir
Diff against target: 528 lines (+477/-2)
5 files modified
examples/CMakeLists.txt (+1/-0)
examples/server_example.cpp (+2/-1)
examples/server_example_fullscreen_placement_strategy.h (+1/-1)
examples/server_example_window_manager.cpp (+440/-0)
examples/server_example_window_manager.h (+33/-0)
To merge this branch: bzr merge lp:~alan-griffiths/mir/tiling-window-mamagement
Reviewer Review Type Date Requested Status
Kevin DuBois (community) Approve on 2015-01-05
Robert Carr (community) Approve on 2015-01-05
PS Jenkins bot continuous-integration Approve on 2015-01-05
Alberto Aguirre 2014-12-19 Approve on 2015-01-05
Daniel van Vugt Needs Information on 2014-12-29
Review via email: mp+245228@code.launchpad.net

Commit Message

examples: Add a (simplistic) tiling window management algorithm to mir_demo_server

Description of the Change

examples: Add a (simplistic) tiling window management algorithm to mir_demo_server

The tiling algorithm is more about being quick to implement than useful.

I'm not entirely happy with the hoops I went through to obtain information (e.g. look how I get the positions of the displays). But this does show that we can get the essential information needed through public interfaces.

To post a comment you must log in.
Alberto Aguirre (albaguirre) wrote :

371 + static auto const description = "window management strategy [{tiling}]";
383 + if (!options->is_set(option))

This is misleading because the mir_demo_server help output implies that tiling is the default window management strategy. Maybe {none, tiling}?

review: Needs Fixing
Daniel van Vugt (vanvugt) wrote :

It's good mir_demo_server is distinguishing itself from mir_demo_server_minimal.

Although it sounds like "tiling" could become the defining feature here, so maybe we should rename mir_demo_server to mir_demo_server_tiling now.

review: Needs Information
2185. By Alan Griffiths on 2015-01-05

merge lp:mir

2186. By Alan Griffiths on 2015-01-05

Code cleanup

2187. By Alan Griffiths on 2015-01-05

Code cleanup

2188. By Alan Griffiths on 2015-01-05

Extend WindowManager to have a "fullscreen" option

2189. By Alan Griffiths on 2015-01-05

Remove redundant --fullscreen-surfaces option from server example

2190. By Alan Griffiths on 2015-01-05

A (slightly) cleaner version

2191. By Alan Griffiths on 2015-01-05

Braces into window-manager options

Alan Griffiths (alan-griffiths) wrote :

> It's good mir_demo_server is distinguishing itself from
> mir_demo_server_minimal.
>
> Although it sounds like "tiling" could become the defining feature here, so
> maybe we should rename mir_demo_server to mir_demo_server_tiling now.

I don't think the optional tiling window management is any more a defining feature than the optional glog logging - it just illustrates how to approach writing a window manager.

Alan Griffiths (alan-griffiths) wrote :

> 371 + static auto const description = "window management strategy
> [{tiling}]";
> 383 + if (!options->is_set(option))
>
> This is misleading because the mir_demo_server help output implies that tiling
> is the default window management strategy. Maybe {none, tiling}?

I'm not sure how to address this. The output was:

  --window-manager arg window management strategy [{tiling}]

Which clearly doesn't show a default. That would be:

  --window-manager arg (=tiling) window management strategy [{tiling}]

In any case we now have:

  --window-manager arg window management strategy
                                        [{tiling|fullscreen}]

Which might allay your concerns

Alberto Aguirre (albaguirre) wrote :

Ok.

review: Approve
Alan Griffiths (alan-griffiths) wrote :

A bit of downstream feedback:

<alan_g> Do you have an opinion on my example above? After writing my WindowManager example code think there's too much "wiring" in the example and not enough in the library. (I could push the "Tracker" and "Factory" wiring into Mir and give it a msh::WindowManger interface)
<greyback_> I do like the basic design of WindowManager, and think TilingWindowManager is a nicely written bit of example code. It is a bit surprising for me that different methods of WindowManager will be called in different threads tho, but as long as that's clearly documented it is fine
<greyback_> yeah the tracker is pretty typical, I had expected it to be in Mir
<greyback_> if you intend to push WindowManager into Mir, then yeah think it logical the Trackers will have to go in too

Robert Carr (robertcarr) wrote :

Ok to me.

I am imagining that this WindowManager interface if pushed in to Mir (or if this tiling WM example continued to grow) would gain configure_surface as a controller based alternative to the managed surface branch?

DisplayTracker stuff is weird :p I am thinking we could make msh::DisplayLayout more useful...e.g. ability to add an observer and query display conf by ID.

review: Approve
Kevin DuBois (kdub) wrote :

Okay by me, in that it shows we can actually swap out the placement of surfaces.

This will probably limit how often the overlay scenario is in use on the phone (as we currently cannot scale the surface as an overlay without a resize)

It seems like we're not requiring a single idea of the placement of surfaces. e.g. if the user doesn't call this:
250 + surface->move_to(old_pos + displacement);
then there could be a split the idea of "where the surface is" into the idea in ms::BasicSurface, and the external tracking system that's being used.

To put another way, they could juggle the SurfaceId's to make a second surfacestack (and core mir loses the idea of where the surfaces are, what monitors its really on, etc). I'm okay with this in general (eg, if one wants to do a fancy custom graphics effect or something), but I'm not sure if we've spelled out the requirements to the users of what core mir needs to know.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'examples/CMakeLists.txt'
2--- examples/CMakeLists.txt 2014-12-19 21:52:32 +0000
3+++ examples/CMakeLists.txt 2015-01-05 14:51:27 +0000
4@@ -25,6 +25,7 @@
5 server_example_input_filter.cpp
6 server_example_fullscreen_placement_strategy.cpp
7 server_example_host_lifecycle_event.cpp
8+ server_example_window_manager.cpp
9 )
10
11 target_link_libraries(eglapp
12
13=== modified file 'examples/server_example.cpp'
14--- examples/server_example.cpp 2014-12-19 21:52:32 +0000
15+++ examples/server_example.cpp 2015-01-05 14:51:27 +0000
16@@ -22,6 +22,7 @@
17 #include "server_example_fullscreen_placement_strategy.h"
18 #include "server_example_display_configuration_policy.h"
19 #include "server_example_host_lifecycle_event_listener.h"
20+#include "server_example_window_manager.h"
21 #include "server_example_test_client.h"
22
23 #include "mir/server.h"
24@@ -90,7 +91,7 @@
25 me::add_display_configuration_options_to(server);
26 me::add_log_host_lifecycle_option_to(server);
27 me::add_glog_options_to(server);
28- me::add_fullscreen_option_to(server);
29+ me::add_window_manager_option_to(server);
30 add_launcher_option_to(server);
31 add_timeout_option_to(server);
32
33
34=== modified file 'examples/server_example_fullscreen_placement_strategy.h'
35--- examples/server_example_fullscreen_placement_strategy.h 2014-12-11 02:30:45 +0000
36+++ examples/server_example_fullscreen_placement_strategy.h 2015-01-05 14:51:27 +0000
37@@ -34,7 +34,7 @@
38
39 namespace examples
40 {
41-class FullscreenPlacementStrategy : public scene::PlacementStrategy
42+class FullscreenPlacementStrategy : public virtual scene::PlacementStrategy
43 {
44 public:
45 FullscreenPlacementStrategy(std::shared_ptr<shell::DisplayLayout> const& display_layout);
46
47=== added file 'examples/server_example_window_manager.cpp'
48--- examples/server_example_window_manager.cpp 1970-01-01 00:00:00 +0000
49+++ examples/server_example_window_manager.cpp 2015-01-05 14:51:27 +0000
50@@ -0,0 +1,440 @@
51+/*
52+ * Copyright © 2014 Canonical Ltd.
53+ *
54+ * This program is free software: you can redistribute it and/or modify it
55+ * under the terms of the GNU General Public License version 3,
56+ * as published by the Free Software Foundation.
57+ *
58+ * This program is distributed in the hope that it will be useful,
59+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
60+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
61+ * GNU General Public License for more details.
62+ *
63+ * You should have received a copy of the GNU General Public License
64+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
65+ *
66+ * Authored By: Alan Griffiths <alan@octopull.co.uk>
67+ */
68+
69+#include "server_example_window_manager.h"
70+#include "server_example_fullscreen_placement_strategy.h"
71+
72+#include "mir/abnormal_exit.h"
73+#include "mir/server.h"
74+#include "mir/compositor/display_buffer_compositor_factory.h"
75+#include "mir/compositor/display_buffer_compositor.h"
76+#include "mir/geometry/rectangles.h"
77+#include "mir/geometry/displacement.h"
78+#include "mir/graphics/display_buffer.h"
79+#include "mir/options/option.h"
80+#include "mir/scene/observer.h"
81+#include "mir/scene/session.h"
82+#include "mir/scene/surface.h"
83+#include "mir/scene/placement_strategy.h"
84+#include "mir/scene/surface_creation_parameters.h"
85+#include "mir/shell/session_coordinator_wrapper.h"
86+#include "mir/shell/surface_coordinator_wrapper.h"
87+
88+#include <algorithm>
89+#include <map>
90+#include <mutex>
91+
92+namespace mc = mir::compositor;
93+namespace me = mir::examples;
94+namespace mf = mir::frontend;
95+namespace mg = mir::graphics;
96+namespace ms = mir::scene;
97+namespace msh = mir::shell;
98+using namespace mir::geometry;
99+
100+///\example server_example_window_manager.cpp
101+/// Demonstrate a simple window management strategy
102+
103+namespace
104+{
105+class WindowManager : public virtual ms::PlacementStrategy
106+{
107+public:
108+
109+ virtual void add_surface(
110+ std::shared_ptr<ms::Surface> const& surface,
111+ ms::Session* session) = 0;
112+
113+ virtual void remove_surface(std::weak_ptr<ms::Surface> const& surface) = 0;
114+
115+ virtual void add_session(std::shared_ptr<mf::Session> const& session) = 0;
116+
117+ virtual void remove_session(std::shared_ptr<mf::Session> const& session) = 0;
118+
119+ virtual void add_display(Rectangle const& area) = 0;
120+
121+ virtual void remove_display(Rectangle const& area) = 0;
122+};
123+
124+class FullscreenWindowManager : public WindowManager, me::FullscreenPlacementStrategy
125+{
126+public:
127+ using me::FullscreenPlacementStrategy::FullscreenPlacementStrategy;
128+
129+private:
130+ void add_surface(std::shared_ptr<ms::Surface> const&, ms::Session*) override {}
131+
132+ void remove_surface(std::weak_ptr<ms::Surface> const&) override {}
133+
134+ void add_session(std::shared_ptr<mf::Session> const&) override {}
135+
136+ void remove_session(std::shared_ptr<mf::Session> const&) override {}
137+
138+ void add_display(Rectangle const&) override {}
139+
140+ void remove_display(Rectangle const&) override {}
141+};
142+
143+class TilingWindowManager : public WindowManager
144+{
145+ auto place(ms::Session const& session, ms::SurfaceCreationParameters const& request_parameters)
146+ -> ms::SurfaceCreationParameters override
147+ {
148+ auto parameters = request_parameters;
149+
150+ std::lock_guard<decltype(mutex)> lock(mutex);
151+ auto const ptile = tiles.find(&session);
152+ if (ptile != end(tiles))
153+ {
154+ Rectangle const& tile = ptile->second;
155+ parameters.top_left = parameters.top_left + (tile.top_left - Point{0, 0});
156+
157+ clip_to_tile(parameters, tile);
158+ }
159+
160+ return parameters;
161+ }
162+
163+ void add_surface(
164+ std::shared_ptr<ms::Surface> const& surface,
165+ ms::Session* session)
166+ {
167+ std::lock_guard<decltype(mutex)> lock(mutex);
168+ surfaces.emplace(session, surface);
169+ }
170+
171+ void remove_surface(std::weak_ptr<ms::Surface> const& /*surface*/)
172+ {
173+ // This looks odd but we want to block in case we're using the surface
174+ std::lock_guard<decltype(mutex)> lock(mutex);
175+ }
176+
177+ void add_session(std::shared_ptr<mf::Session> const& session)
178+ {
179+ std::lock_guard<decltype(mutex)> lock(mutex);
180+ tiles[session.get()] = Rectangle{};
181+ update_tiles();
182+ }
183+
184+ void remove_session(std::shared_ptr<mf::Session> const& session)
185+ {
186+ std::lock_guard<decltype(mutex)> lock(mutex);
187+ tiles.erase(session.get());
188+ surfaces.erase(session.get());
189+ update_tiles();
190+ }
191+
192+ void add_display(Rectangle const& area)
193+ {
194+ std::lock_guard<decltype(mutex)> lock(mutex);
195+ displays.push_back(area);
196+ update_tiles();
197+ }
198+
199+ void remove_display(Rectangle const& area)
200+ {
201+ std::lock_guard<decltype(mutex)> lock(mutex);
202+ auto const i = std::find(begin(displays), end(displays), area);
203+ if (i != end(displays)) displays.erase(i);
204+ update_tiles();
205+ }
206+
207+private:
208+ void update_tiles()
209+ {
210+ if (tiles.size() < 1 || displays.size() < 1) return;
211+
212+ auto const sessions = tiles.size();
213+ Rectangles view;
214+
215+ for (auto const& display : displays)
216+ view.add(display);
217+
218+ auto const bounding_rect = view.bounding_rectangle();
219+
220+ auto const total_width = bounding_rect.size.width.as_int();
221+ auto const total_height = bounding_rect.size.height.as_int();
222+
223+ auto index = 0;
224+
225+ for (auto& tile : tiles)
226+ {
227+ auto const x = (total_width*index)/sessions;
228+ ++index;
229+ auto const dx = (total_width*index)/sessions - x;
230+
231+ auto const old_tile = tile.second;
232+ Rectangle const new_tile{{x, 0}, {dx, total_height}};
233+
234+ update_surfaces(tile.first, old_tile, new_tile);
235+
236+ tile.second = new_tile;
237+ }
238+ }
239+
240+ void update_surfaces(mf::Session const* session, Rectangle const& old_tile, Rectangle const& new_tile)
241+ {
242+ auto displacement = new_tile.top_left - old_tile.top_left;
243+ auto const moved = surfaces.equal_range(session);
244+
245+ for (auto p = moved.first; p != moved.second; ++p)
246+ {
247+ if (auto const surface = p->second.lock())
248+ {
249+ auto const old_pos = surface->top_left();
250+ surface->move_to(old_pos + displacement);
251+
252+ fit_to_new_tile(*surface, old_tile, new_tile);
253+ }
254+ }
255+ }
256+
257+ static void clip_to_tile(ms::SurfaceCreationParameters& parameters, Rectangle const& tile)
258+ {
259+ auto const displacement = parameters.top_left - tile.top_left;
260+
261+ auto width = std::min(tile.size.width.as_int()-displacement.dx.as_int(), parameters.size.width.as_int());
262+ auto height = std::min(tile.size.height.as_int()-displacement.dy.as_int(), parameters.size.height.as_int());
263+
264+ parameters.size = Size{width, height};
265+ }
266+
267+ static void fit_to_new_tile(ms::Surface& surface, Rectangle const& old_tile, Rectangle const& new_tile)
268+ {
269+ auto const displacement = surface.top_left() - new_tile.top_left;
270+
271+ // For now just scale if was filling width/height of tile
272+ auto const old_size = surface.size();
273+ auto const scaled_width = old_size.width == old_tile.size.width ? new_tile.size.width : old_size.width;
274+ auto const scaled_height = old_size.height == old_tile.size.height ? new_tile.size.height : old_size.height;
275+
276+ auto width = std::min(new_tile.size.width.as_int()-displacement.dx.as_int(), scaled_width.as_int());
277+ auto height = std::min(new_tile.size.height.as_int()-displacement.dy.as_int(), scaled_height.as_int());
278+
279+ surface.resize({width, height});
280+ }
281+
282+ std::mutex mutex;
283+ std::vector<Rectangle> displays;
284+ std::map<mf::Session const*, Rectangle> tiles;
285+ std::multimap<mf::Session const*, std::weak_ptr<ms::Surface>> surfaces;
286+};
287+
288+auto const option = "window-manager";
289+auto const description = "window management strategy [{tiling|fullscreen}]";
290+auto const tiling = "tiling";
291+auto const fullscreen = "fullscreen";
292+
293+class WindowManagmentFactory
294+{
295+public:
296+ explicit WindowManagmentFactory(mir::Server& server) : server{server} {}
297+
298+ auto window_manager() -> std::shared_ptr<WindowManager>
299+ {
300+ auto tmp = wm.lock();
301+
302+ if (!tmp)
303+ {
304+ auto const options = server.get_options();
305+ auto const selection = options->get<std::string>(option);
306+
307+ if (selection == tiling)
308+ tmp = std::make_shared<TilingWindowManager>();
309+ else if (selection == fullscreen)
310+ tmp = std::make_shared<FullscreenWindowManager>(server.the_shell_display_layout());
311+ else
312+ throw mir::AbnormalExit("Unknown window manager: " + selection);
313+
314+ wm = tmp;
315+ }
316+
317+ return tmp;
318+ }
319+
320+private:
321+ mir::Server& server;
322+ std::weak_ptr<WindowManager> wm;
323+};
324+
325+class SessionTracker : public msh::SessionCoordinatorWrapper
326+{
327+public:
328+ SessionTracker(
329+ std::shared_ptr<ms::SessionCoordinator> const& wrapped,
330+ std::shared_ptr<WindowManager> const& window_manager) :
331+ msh::SessionCoordinatorWrapper(wrapped),
332+ window_manager(window_manager)
333+ {
334+ }
335+
336+private:
337+ auto open_session(pid_t client_pid, std::string const& name, std::shared_ptr<mf::EventSink> const& sink)
338+ -> std::shared_ptr<mf::Session> override
339+ {
340+ auto const new_session = msh::SessionCoordinatorWrapper::open_session(client_pid, name, sink);
341+ window_manager->add_session(new_session);
342+ return new_session;
343+ }
344+
345+ void close_session(std::shared_ptr<mf::Session> const& session) override
346+ {
347+ window_manager->remove_session(session);
348+ msh::SessionCoordinatorWrapper::close_session(session);
349+ }
350+
351+ std::shared_ptr<WindowManager> const window_manager;
352+};
353+
354+class SurfaceTracker : public msh::SurfaceCoordinatorWrapper
355+{
356+public:
357+ SurfaceTracker(
358+ std::shared_ptr<ms::SurfaceCoordinator> const& wrapped,
359+ std::shared_ptr<WindowManager> const& window_manager) :
360+ msh::SurfaceCoordinatorWrapper(wrapped),
361+ window_manager(window_manager)
362+ {
363+ }
364+
365+private:
366+ auto add_surface(ms::SurfaceCreationParameters const& params, ms::Session* session)
367+ -> std::shared_ptr<ms::Surface> override
368+ {
369+ auto const new_surface = msh::SurfaceCoordinatorWrapper::add_surface(params, session);
370+ window_manager->add_surface(new_surface, session);
371+ return new_surface;
372+ }
373+
374+ void remove_surface(std::weak_ptr<ms::Surface> const& surface)
375+ {
376+ window_manager->remove_surface(surface);
377+ msh::SurfaceCoordinatorWrapper::remove_surface(surface);
378+ }
379+
380+ std::shared_ptr<WindowManager> const window_manager;
381+};
382+
383+class DisplayTracker : public mc::DisplayBufferCompositor
384+{
385+public:
386+ DisplayTracker(
387+ std::unique_ptr<mc::DisplayBufferCompositor>&& wrapped,
388+ Rectangle const& area,
389+ std::shared_ptr<WindowManager> const& window_manager) :
390+ wrapped{std::move(wrapped)},
391+ area{area},
392+ window_manager(window_manager)
393+ {
394+ window_manager->add_display(area);
395+ }
396+
397+ ~DisplayTracker() noexcept
398+ {
399+ window_manager->remove_display(area);
400+ }
401+
402+private:
403+
404+ void composite(mc::SceneElementSequence&& scene_sequence) override
405+ {
406+ wrapped->composite(std::move(scene_sequence));
407+ }
408+
409+ std::unique_ptr<mc::DisplayBufferCompositor> const wrapped;
410+ Rectangle const area;
411+ std::shared_ptr<WindowManager> const window_manager;
412+};
413+
414+class DisplayTrackerFactory : public mc::DisplayBufferCompositorFactory
415+{
416+public:
417+ DisplayTrackerFactory(
418+ std::shared_ptr<mc::DisplayBufferCompositorFactory> const& wrapped,
419+ std::shared_ptr<WindowManager> const& window_manager) :
420+ wrapped{wrapped},
421+ window_manager(window_manager)
422+ {
423+ }
424+
425+private:
426+ std::unique_ptr<mc::DisplayBufferCompositor> create_compositor_for(mg::DisplayBuffer& display_buffer)
427+ {
428+ auto compositor = wrapped->create_compositor_for(display_buffer);
429+ return std::unique_ptr<mc::DisplayBufferCompositor>{
430+ new DisplayTracker{std::move(compositor), display_buffer.view_area(), window_manager}};
431+ }
432+
433+ std::shared_ptr<mc::DisplayBufferCompositorFactory> const wrapped;
434+ std::shared_ptr<WindowManager> const window_manager;
435+};
436+}
437+
438+void me::add_window_manager_option_to(Server& server)
439+{
440+ server.add_configuration_option(option, description, mir::OptionType::string);
441+
442+ auto const factory = std::make_shared<WindowManagmentFactory>(server);
443+
444+ server.override_the_placement_strategy([factory, &server]()
445+ -> std::shared_ptr<ms::PlacementStrategy>
446+ {
447+ auto const options = server.get_options();
448+
449+ if (!options->is_set(option))
450+ return std::shared_ptr<ms::PlacementStrategy>{};
451+
452+ return factory->window_manager();
453+ });
454+
455+ server.wrap_session_coordinator([factory, &server]
456+ (std::shared_ptr<ms::SessionCoordinator> const& wrapped)
457+ -> std::shared_ptr<ms::SessionCoordinator>
458+ {
459+ auto const options = server.get_options();
460+
461+ if (!options->is_set(option))
462+ return wrapped;
463+
464+ return std::make_shared<SessionTracker>(wrapped, factory->window_manager());
465+ });
466+
467+ server.wrap_surface_coordinator([factory, &server]
468+ (std::shared_ptr<ms::SurfaceCoordinator> const& wrapped)
469+ -> std::shared_ptr<ms::SurfaceCoordinator>
470+ {
471+ auto const options = server.get_options();
472+
473+ if (!options->is_set(option))
474+ return wrapped;
475+
476+ return std::make_shared<SurfaceTracker>(wrapped, factory->window_manager());
477+ });
478+
479+ server.wrap_display_buffer_compositor_factory([factory, &server]
480+ (std::shared_ptr<mc::DisplayBufferCompositorFactory> const& wrapped)
481+ -> std::shared_ptr<mc::DisplayBufferCompositorFactory>
482+ {
483+ auto const options = server.get_options();
484+
485+ if (!options->is_set(option))
486+ return wrapped;
487+
488+ return std::make_shared<DisplayTrackerFactory>(wrapped, factory->window_manager());
489+ });
490+}
491
492=== added file 'examples/server_example_window_manager.h'
493--- examples/server_example_window_manager.h 1970-01-01 00:00:00 +0000
494+++ examples/server_example_window_manager.h 2015-01-05 14:51:27 +0000
495@@ -0,0 +1,33 @@
496+/*
497+ * Copyright © 2014 Canonical Ltd.
498+ *
499+ * This program is free software: you can redistribute it and/or modify it
500+ * under the terms of the GNU General Public License version 3,
501+ * as published by the Free Software Foundation.
502+ *
503+ * This program is distributed in the hope that it will be useful,
504+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
505+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
506+ * GNU General Public License for more details.
507+ *
508+ * You should have received a copy of the GNU General Public License
509+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
510+ *
511+ * Authored By: Alan Griffiths <alan@octopull.co.uk>
512+ */
513+
514+#ifndef MIR_EXAMPLES_WINDOW_MANAGER_H_
515+#define MIR_EXAMPLES_WINDOW_MANAGER_H_
516+
517+namespace mir
518+{
519+class Server;
520+
521+namespace examples
522+{
523+void add_window_manager_option_to(Server& server);
524+}
525+} // namespace mir
526+
527+
528+#endif // MIR_EXAMPLES_WINDOW_MANAGER_H_

Subscribers

People subscribed via source and target branches