Mir

Merge lp:~mir-team/mir/improved-tiling-window-mamagement into lp:mir

Proposed by Alan Griffiths on 2015-01-09
Status: Merged
Approved by: Cemil Azizoglu on 2015-01-14
Approved revision: 2244
Merged at revision: 2223
Proposed branch: lp:~mir-team/mir/improved-tiling-window-mamagement
Merge into: lp:mir
Diff against target: 1550 lines (+1090/-305)
7 files modified
doc/demo_server_controls.md (+60/-0)
doc/mainpage.md (+15/-1)
examples/CMakeLists.txt (+7/-0)
examples/eglstateswitcher.c (+97/-0)
examples/server_example_window_management.cpp (+771/-0)
examples/server_example_window_management.h (+95/-0)
examples/server_example_window_manager.cpp (+45/-304)
To merge this branch: bzr merge lp:~mir-team/mir/improved-tiling-window-mamagement
Reviewer Review Type Date Requested Status
Andreas Pokorny (community) Approve on 2015-01-14
PS Jenkins bot continuous-integration Approve on 2015-01-14
Cemil Azizoglu (community) Approve on 2015-01-13
Alberto Aguirre Approve on 2015-01-13
Alexandros Frantzis (community) Approve on 2015-01-13
Daniel van Vugt Needs Fixing on 2015-01-13
Robert Carr (community) Approve on 2015-01-12
Kevin DuBois (community) 2015-01-09 Approve on 2015-01-12
Review via email: mp+245994@code.launchpad.net

Commit Message

examples: extend the mir_demo_server's TilingWindowManager into something more functional

Description of the Change

examples: extend the mir_demo_server's TilingWindowManager into something more functional

To post a comment you must log in.
Daniel van Vugt (vanvugt) wrote :

Looks like some fun.

Just a slight word of caution: When you do demonstrate fancy things in demos, it's helpful if as much of the core logic resides in Mir itself and not in the demo. So that future shells don't all need to reinvent the wheel. One of the main architectural decisions that distinguishes Mir from Wayland is that we (intend to) provide core logic for shells instead of mandating that everyone write their own each time.

Alan Griffiths (alan-griffiths) wrote :

> Looks like some fun.
>
> Just a slight word of caution: When you do demonstrate fancy things in demos,
> it's helpful if as much of the core logic resides in Mir itself and not in the
> demo. So that future shells don't all need to reinvent the wheel. One of the
> main architectural decisions that distinguishes Mir from Wayland is that we
> (intend to) provide core logic for shells instead of mandating that everyone
> write their own each time.

It is good to show that future shells are at least possible without access to internals and having this for folks to play with gives an idea of some of the pain points with the current API.

And yeah, there's PITA stuff here (that I think should be supported more directly by Mir). I intend to address that when we feel confident of the right APIs to commit to supporting.

Kevin DuBois (kdub) wrote :

LGTM. It seems useful to see what we can do (and show others what can be done) with the existing API. I hope that we would drive the public api by porting universally useful things to core mir from the examples (via acceptance tests).

review: Approve
Robert Carr (robertcarr) wrote :

LGTM. I think this provides a lot of useful guidance on how to continue refactoring core Mir components: Ill share an edited version of my thoughts from an email thread.

Investigating the me::WindowManager interface...it's easy to recognize a clump of methods:
add_session/remove_session/add_surface/remove_surface

as paralleling the mf::Shell (SessionManager) interface to some extent. Though immediately we can recognize it as a clearer distinction of responsibilities: We no longer require the 'Window Manager' to act as the Surface factory, merely a Surface controller.

I think with this mindset we can perhaps see a good refactoring of SessionManager and the SurfaceFactory hierarchy. mf::Shell could be changed to parallel this me::WindowManager interface directly, and the factory responsibilities could be given to a new interface. The mf::Shell implementation could be given responsibility for adding the surface to the scene. Not only does this sort out the strange observer based flow in this branch[1] but it simplifies things such as the PlacementStrategy...a weird wart of a controller as it stands.

[1] The pattern used here of the window manager responding to observer notifications from the scene probably does not scale...as the window manager can not enforce that it is able to respond to an observer notification to enforce the correct policy before the compositor visits the scene to paint. Thus we will eventually have to migrate it to be a true view controller.

Robert Carr (robertcarr) :
review: Approve
Robert Carr (robertcarr) wrote :
Download full text (4.0 KiB)

For purposes of conversational transparency ill share the older version of my thoughts too, though I think what I just wrote is more significantly succinct:

Hey Alan :) Thanks for leading this effort. I've been thinking about the me::WindowManager interface and how it connects to the rest of the system. I think it's a good step and more promising than surface wrapper...I think there's one sort of cleanup that should happen before its totally compelling though.

Scanning through me::WindowManager, the first methods which grab my attention are:
add_surface, remove_surface, add_session, remove_session, etc...

Then of course, I am reminded of mf::Shell and the SessionManager When you dust off those forgotten classes this flow looks kind of strange: from SessionMediator through mf::Shell which does some factory stuff and adds stuff to the scene which dispatches to some observers, observers invoke me::WindowManager which copies the session/surface map held by the mf::Shell implementation and has a grand time with the scene implementing policy. In addition to being complicated I think this is pragmatically problematic: In particular adding surfaces to the scene and then notifying the window manager seems vulnerable to races in terms of the window manager having time to act before the compositor visits the scene. Various locking strategies can solve this, but problems continue to arise as long as the window manger is the observer....

I think this is pretty easy to sort out though if we extract responsibilities from SessionManager. First we can extract the Session and Surface Factory role/roles. This factory can then be given to the session mediator. This will be a true factory and NOT responsible for notifying observers, giving focus to new surfaces, or even adding them to the scene! Now the mf::Shell interface is reshaped to look like portions of your WindowManager interface, e.g.

mf::Shell
{
add_session...
remove_session,
add_surface,.
remove_surface,
configure_surface_attribute,
resize_surface, (assuming we had client initiated resize)
etc...
}

me::WindowManager could of course implement this. This object can then add surfaces to the scene as it wishes, and manipulate them as such. Elements which are common to all window managers have a representation in the protocol, and thus will be part of this mf::Shell interface. Shell authors are free to extend this interface in their implementation class to attach additional methods (e.g. drag and resize as they exist in me::WindowManager or unity::WindowManager::start_window_carousel_for(session)).

In some ways this is a return to some of the original window management design in the sense that it proposes this object sitting in-between the front-end and scene acting as an enforcer of policy through a controller role. It features a few key differences though that I think will really clarify the code and make things easier:

1. Removal of the msh::Surface wrapper. Maybe you remember a model we used to use where msh::Surface would hold so called surface data which wasn't part of the 'inner core' e.g. SurfaceAttributes. This was to allow the implementation of surfaces which were not "Shell" surfaces and...

Read more...

Cemil Azizoglu (cemil-azizoglu) wrote :

F11 key doesn't do anything for me. It was working in the previous versions, though resize, and move both work. Is it still working for you?

review: Needs Fixing
Cemil Azizoglu (cemil-azizoglu) wrote :

Also eglflash doesn't toggle for me. It comes up maximized and stays maximized - unless I resize it, then it starts toggling.

Alan Griffiths (alan-griffiths) wrote :

I guess it is confusing that eglflash comes up in a "restored" window that is the size of the tile.

On 12 Jan 2015 22:45, Cemil Azizoglu <email address hidden> wrote:
>
> Also eglflash doesn't toggle for me. It comes up maximized and stays maximized - unless I resize it, then it starts toggling.
> --
> https://code.launchpad.net/~mir-team/mir/improved-tiling-window-mamagement/+merge/245994
> Your team Mir development team is subscribed to branch lp:mir.

Chris Halse Rogers (raof) wrote :

I think I've pretty consistently said that we should implement a window manager with what we've got and then pull obviously relevant bits into Mir core over time. As such, I approve of this general approach. (Have not yet done code review)

Daniel van Vugt (vanvugt) wrote :

(1) Please don't make eglflash do anything other than flash:
158 +++ examples/eglflash.c 2015-01-09 18:07:10 +0000
164 + mir_eglapp_toggle_surface_state();

(2) These odd state transitions shouldn't be common to all eglapps. It results in such weird behaviour that it would be better placed in some kind of explicitly named mir_demo_client_shapeshifter...
114 +++ examples/eglapp.c 2015-01-09 18:07:10 +0000
120 +void mir_eglapp_toggle_surface_state(void)

(3) The class name "WindowManager" is a hack and really needs to be removed instead of reused. I've mentioned this several times over the years. Just once last year I slipped up and felt I had to implement a "WindowManager" class. But we don't and we should not.
   "Manager" like "Controller" are poorly chosen abstractions that lead to poor designs that are difficult to work with. But I'm not the only one who says so. Further reading:
   http://objology.blogspot.com.au/2011/09/one-of-best-bits-of-programming-advice.html
   http://www.benhallbenhall.com/2013/01/naming-objects-er-object-names/
I don't expect to get sufficient votes to affect change on (3) but it's important and something everyone on the team should think about.

review: Needs Fixing
2235. By Alan Griffiths on 2015-01-13

eglstateswitcher

Alan Griffiths (alan-griffiths) wrote :

> (1) Please don't make eglflash do anything other than flash:
> 158 +++ examples/eglflash.c 2015-01-09 18:07:10 +0000
> 164 + mir_eglapp_toggle_surface_state();
>
> (2) These odd state transitions shouldn't be common to all eglapps. It results
> in such weird behaviour that it would be better placed in some kind of
> explicitly named mir_demo_client_shapeshifter...
> 114 +++ examples/eglapp.c 2015-01-09 18:07:10 +0000
> 120 +void mir_eglapp_toggle_surface_state(void)

Fixed

> (3) The class name "WindowManager" is a hack and really needs to be removed
> instead of reused. I've mentioned this several times over the years. Just once
> last year I slipped up and felt I had to implement a "WindowManager" class.
> But we don't and we should not.
> "Manager" like "Controller" are poorly chosen abstractions that lead to
> poor designs that are difficult to work with. But I'm not the only one who
> says so. Further reading:
> http://objology.blogspot.com.au/2011/09/one-of-best-bits-of-programming-
> advice.html
> http://www.benhallbenhall.com/2013/01/naming-objects-er-object-names/
> I don't expect to get sufficient votes to affect change on (3) but it's
> important and something everyone on the team should think about.

I too have doubts about XXXManager, but "window manager" seems to have sufficient currency to be exempted as a "term of art".

2236. By Alan Griffiths on 2015-01-13

Avoid attempt to recursively lock

Alan Griffiths (alan-griffiths) wrote :

> F11 key doesn't do anything for me. It was working in the previous versions,
> though resize, and move both work. Is it still working for you?

Fixed

2237. By Alan Griffiths on 2015-01-13

Further reduce scope of lock

2238. By Alan Griffiths on 2015-01-13

Clearer code

2239. By Alan Griffiths on 2015-01-13

Remove redundant resizing code

Alexandros Frantzis (afrantzis) wrote :

Looks like a good concrete starting point to trigger further thoughts and discussions about the architecture of our scene/model and how it ties with window management.

review: Approve
Alberto Aguirre (albaguirre) wrote :

Does what it claims to do.

It's a good showcase of the awkward APIs the shell has to use to do simple things, which hopefully guides our future architectural changes.

review: Approve
2240. By Cemil Azizoglu on 2015-01-13

On MacBook Pro, F11 is FN+F11. So don't use function key in the mask.

Cemil Azizoglu (cemil-azizoglu) wrote :

> F11 key doesn't do anything for me. It was working in the previous versions,
> though resize, and move both work. Is it still working for you?

Fixed.

review: Approve
Robert Carr (robertcarr) wrote :

*Opens sealed envelope over suspenseful drum roll*

*Reveals prediction of review votes on this MP to shock and applause*

Thank you, thank you, I'm here all week.

Daniel van Vugt (vanvugt) wrote :

(4) Preferably choose key combos already familiar to users, instead of:
45 + - Resize window: *Meta-mousewheel*
Normally Alt+middle_button
46 + - Maximize/restore current window (to tile size): F11
Needs a modifier like Alt or something. Because it's good behaviour to not steal keys that apps might use themselves (e.g. F11 usually means app-initiated fullscreen).

(5) I'm a little concerned that people see this as an alternative to my current and previous proposals. They do different things of course, so remember that. My branches provide WM logic that is immediately reusable (without any effort) by any shell in half the amount of code, with test cases, and also solve future problems of metadata, behavioural overrides and minimizing the footprint of future shells that just want to override a few things.
  I understand this tiling work is still acting as a thought experiment, but it appears to be on track to never be able to catch up to what I've already proposed in all those metrics. Of course, not sure, and it's just examples, so not a blocking concern yet.

2241. By Alan Griffiths on 2015-01-14

merge lp:mir

Alan Griffiths (alan-griffiths) wrote :

> (4) Preferably choose key combos already familiar to users, instead of:
> 45 + - Resize window: *Meta-mousewheel*
> Normally Alt+middle_button

I'll look into this. You mean Alt+middle_button & drag I presume?

> 46 + - Maximize/restore current window (to tile size): F11
> Needs a modifier like Alt or something. Because it's good behaviour to not
> steal keys that apps might use themselves (e.g. F11 usually means app-
> initiated fullscreen).

OK, done.

> (5) I'm a little concerned that people see this as an alternative to my
> current and previous proposals. They do different things of course, so
> remember that. My branches provide WM logic that is immediately reusable
> (without any effort) by any shell in half the amount of code, with test cases,
> and also solve future problems of metadata, behavioural overrides and
> minimizing the footprint of future shells that just want to override a few
> things.
> I understand this tiling work is still acting as a thought experiment, but
> it appears to be on track to never be able to catch up to what I've already
> proposed in all those metrics. Of course, not sure, and it's just examples, so
> not a blocking concern yet.

I agree this isn't an alternative. "Tiling" is an experiment to see how well shells can do window management through our published API. Your "alternative" proposals work use internal APIs so it isn't clear how they can be used by "future shells" (you're welcome to add options to demo_server to clarify this).

IMO both this work and yours is likely to motivate changes to the public API to make it easier to use.

2242. By Alan Griffiths on 2015-01-14

Maximize/restore current window (to tile size): F11 => Alt-F11

2243. By Alan Griffiths on 2015-01-14

Alt-middle_button drag to resize

2244. By Alan Griffiths on 2015-01-14

Remove old scaling code

Alan Griffiths (alan-griffiths) wrote :

> > (4) Preferably choose key combos already familiar to users, instead of:
> > 45 + - Resize window: *Meta-mousewheel*
> > Normally Alt+middle_button
>
> I'll look into this. You mean Alt+middle_button & drag I presume?

Done

Andreas Pokorny (andreas-pokorny) wrote :

nice.

http://xmonad.org/tour.html#tiling
^
If you feel the urge for a different layout/tiling strategies

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

> nice.
>
> http://xmonad.org/tour.html#tiling
> ^
> If you feel the urge for a different layout/tiling strategies

That can be an exercise for the interested reader. ;^)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'doc/demo_server_controls.md'
2--- doc/demo_server_controls.md 1970-01-01 00:00:00 +0000
3+++ doc/demo_server_controls.md 2015-01-14 12:09:57 +0000
4@@ -0,0 +1,60 @@
5+Demo Server {#demo_server}
6+===========
7+
8+Mir's demo server (`mir_demo_server`) is an example of using the Mir to
9+build a server. In contrast to mir_proving_server (see \ref demo_shell_controls)
10+it uses only functionality supported by the public Mir API.
11+
12+Running Demo Server
13+-------------------
14+
15+Remember to always run `mir_demo_server` as root on PC (not required on
16+Android), as this is required for input device support (open bug
17+https://bugs.launchpad.net/mir/+bug/1286252);
18+
19+ sudo mir_demo_server
20+
21+And if you're not already on the VT you wish to use, that needs to be
22+specified:
23+
24+ sudo mir_demo_server --vt 1
25+
26+There are plenty more options available if you run:
27+
28+ mir_demo_server --help
29+
30+Tiling Window Manager
31+---------------------
32+
33+One option that needs elaboration is "--window-manager tiling".
34+
35+This starts a (rather primitive) tiling window manager. It tracks the available
36+displays and splits the available workspace into "tiles" (one per client).
37+
38+The following operations are supported:
39+
40+ - Quit (shut down the Mir server): *Ctrl-Alt-Backspace*
41+ - Switch back to X: *Ctrl-Alt-F7*
42+ - Switch virtual terminals (VTs): *Ctrl-Alt-(F1-F12)*
43+ - Switch apps: tap or click on the corresponding tile
44+ - Move window: *Alt-leftmousebutton* drag
45+ - Resize window: *Alt-middle_button* drag
46+ - Maximize/restore current window (to tile size): Alt-F11
47+ - Maximize/restore current window (to tile height): Shift-F11
48+ - Maximize/restore current window (to tile width): Ctrl-F11
49+
50+For those writing client code request to set the surface attribute
51+`mir_surface_attrib_state` are honoured:
52+ - `mir_surface_state_restored`: restores the window
53+ - `mir_surface_state_maximized`: maximizes to tile size
54+ - `mir_surface_state_vertmaximized`: maximizes to tile height
55+
56+For a quick demo try:
57+
58+ sudo mir_demo_server --vt 1 --display-config sidebyside\
59+ --window-manager tiling --launch bin/mir_demo_client_egltriangle\
60+ --test-client bin/mir_demo_client_multiwin --test-timeout 60
61+
62+(Remember to unwrap the line)
63+
64+Want more? Log your requests at: https://bugs.launchpad.net/mir/+filebug
65
66=== modified file 'doc/mainpage.md'
67--- doc/mainpage.md 2014-12-28 08:33:45 +0000
68+++ doc/mainpage.md 2015-01-14 12:09:57 +0000
69@@ -42,6 +42,7 @@
70 - \ref using_mir_on_android
71 - \ref debug_for_xmir
72 - \ref demo_shell_controls
73+ - \ref demo_server
74
75 Getting involved
76 ----------------
77@@ -81,7 +82,20 @@
78 Mir server is written as a library which allows the server code to be adapted
79 for bespoke applications.
80
81- - \subpage render_surfaces-example "render_surfaces.cpp: A simple program using the Mir library"
82+ - \subpage server_example.cpp
83+ "server_example.cpp: a test executable hosting the following"
84+ - \subpage server_example_input_event_filter.cpp
85+ "server_example_input_event_filter.cpp: provide a Quit command"
86+ - \subpage server_example_display_configuration_policy.cpp
87+ "server_example_display_configuration_policy.cpp: configuring display layout"
88+ - \subpage server_example_input_filter.cpp
89+ "server_example_input_filter.cpp: print input events to stdout"
90+ - \subpage server_example_log_options.cpp
91+ "server_example_log_options.cpp: replace Mir logger with glog"
92+ - \subpage server_example_window_manager.cpp
93+ "server_example_window_manager.cpp: How to wire up a window manager"
94+ - \subpage server_example_window_management.cpp
95+ "server_example_window_management.cpp: simple window management examples"
96
97 Working on Mir code
98 -------------------
99
100=== modified file 'examples/CMakeLists.txt'
101--- examples/CMakeLists.txt 2015-01-14 08:52:38 +0000
102+++ examples/CMakeLists.txt 2015-01-14 12:09:57 +0000
103@@ -23,6 +23,7 @@
104 server_example_fullscreen_placement_strategy.cpp
105 server_example_host_lifecycle_event.cpp
106 server_example_window_manager.cpp
107+ server_example_window_management.cpp
108 )
109
110 target_link_libraries(eglapp
111@@ -60,6 +61,12 @@
112 target_link_libraries(mir_demo_client_cursors
113 eglapp
114 )
115+mir_add_wrapped_executable(mir_demo_client_eglstateswitcher
116+ eglstateswitcher.c
117+)
118+target_link_libraries(mir_demo_client_eglstateswitcher
119+ eglapp
120+)
121
122 mir_add_wrapped_executable(mir_demo_client_basic
123 basic.c
124
125=== added file 'examples/eglstateswitcher.c'
126--- examples/eglstateswitcher.c 1970-01-01 00:00:00 +0000
127+++ examples/eglstateswitcher.c 2015-01-14 12:09:57 +0000
128@@ -0,0 +1,97 @@
129+/*
130+ * Trivial GL demo; switches surface states. Showing how simple life is with eglapp.
131+ *
132+ * Copyright © 2014 Canonical Ltd.
133+ *
134+ * This program is free software: you can redistribute it and/or modify
135+ * it under the terms of the GNU General Public License version 3 as
136+ * published by the Free Software Foundation.
137+ *
138+ * This program is distributed in the hope that it will be useful,
139+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
140+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
141+ * GNU General Public License for more details.
142+ *
143+ * You should have received a copy of the GNU General Public License
144+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
145+ *
146+ * Author: Alan Griffiths <alan@octopull.co.uk>
147+ */
148+
149+#include "eglapp.h"
150+#include <mir_toolkit/mir_client_library.h>
151+
152+#include <stdio.h>
153+#include <unistd.h>
154+#include <GLES2/gl2.h>
155+
156+static void toggle_surface_state(MirSurface* const surface, MirSurfaceState* state);
157+
158+typedef struct Color
159+{
160+ GLfloat r, g, b, a;
161+} Color;
162+
163+int main(int argc, char *argv[])
164+{
165+ float const opacity = mir_eglapp_background_opacity;
166+ Color const orange = {0.866666667f, 0.282352941f, 0.141414141f, opacity};
167+
168+ unsigned int width = 120, height = 120;
169+
170+ if (!mir_eglapp_init(argc, argv, &width, &height))
171+ return 1;
172+
173+ MirSurface* const surface = mir_eglapp_native_surface();
174+ MirSurfaceState state = mir_surface_get_state(surface);
175+
176+ /* This is probably the simplest GL you can do */
177+ while (mir_eglapp_running())
178+ {
179+ glClearColor(orange.r, orange.g, orange.b, orange.a);
180+ glClear(GL_COLOR_BUFFER_BIT);
181+ mir_eglapp_swap_buffers();
182+ sleep(2);
183+
184+ toggle_surface_state(surface, &state);
185+ }
186+
187+ mir_eglapp_shutdown();
188+
189+ return 0;
190+}
191+
192+#define NEW_STATE(new_state)\
193+ puts("Requesting state: " #new_state);\
194+ *state = new_state
195+
196+void toggle_surface_state(MirSurface* const surface, MirSurfaceState* state)
197+{
198+ switch (*state)
199+ {
200+ case mir_surface_state_restored:
201+ NEW_STATE(mir_surface_state_unknown);
202+ break;
203+
204+ case mir_surface_state_minimized:
205+ NEW_STATE(mir_surface_state_restored);
206+ break;
207+
208+ case mir_surface_state_fullscreen:
209+ NEW_STATE(mir_surface_state_vertmaximized);
210+ break;
211+
212+ case mir_surface_state_maximized:
213+ NEW_STATE(mir_surface_state_fullscreen);
214+ break;
215+
216+ case mir_surface_state_vertmaximized:
217+ NEW_STATE(mir_surface_state_minimized);
218+ break;
219+
220+ default:
221+ NEW_STATE(mir_surface_state_maximized);
222+ break;
223+ }
224+ mir_surface_set_state(surface, *state);
225+}
226
227=== added file 'examples/server_example_window_management.cpp'
228--- examples/server_example_window_management.cpp 1970-01-01 00:00:00 +0000
229+++ examples/server_example_window_management.cpp 2015-01-14 12:09:57 +0000
230@@ -0,0 +1,771 @@
231+/*
232+ * Copyright © 2014 Canonical Ltd.
233+ *
234+ * This program is free software: you can redistribute it and/or modify it
235+ * under the terms of the GNU General Public License version 3,
236+ * as published by the Free Software Foundation.
237+ *
238+ * This program is distributed in the hope that it will be useful,
239+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
240+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
241+ * GNU General Public License for more details.
242+ *
243+ * You should have received a copy of the GNU General Public License
244+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
245+ *
246+ * Authored By: Alan Griffiths <alan@octopull.co.uk>
247+ */
248+
249+#include "server_example_window_management.h"
250+#include "server_example_fullscreen_placement_strategy.h"
251+
252+#include "mir/abnormal_exit.h"
253+#include "mir/server.h"
254+#include "mir/geometry/rectangles.h"
255+#include "mir/geometry/displacement.h"
256+#include "mir/input/composite_event_filter.h"
257+#include "mir/options/option.h"
258+#include "mir/scene/session_listener.h"
259+#include "mir/shell/focus_controller.h"
260+
261+#include <linux/input.h>
262+
263+#include <map>
264+#include <vector>
265+#include <mutex>
266+
267+namespace mc = mir::compositor;
268+namespace me = mir::examples;
269+namespace mg = mir::graphics;
270+namespace mi = mir::input;
271+namespace ms = mir::scene;
272+namespace msh = mir::shell;
273+using namespace mir::geometry;
274+
275+///\example server_example_window_management.cpp
276+/// Demonstrate simple window management strategies
277+
278+char const* const me::wm_option = "window-manager";
279+char const* const me::wm_description = "window management strategy [{tiling|fullscreen}]";
280+
281+namespace
282+{
283+char const* const wm_tiling = "tiling";
284+char const* const wm_fullscreen = "fullscreen";
285+
286+// Very simple - make every surface fullscreen
287+class FullscreenWindowManager : public me::WindowManager, me::FullscreenPlacementStrategy
288+{
289+public:
290+ using me::FullscreenPlacementStrategy::FullscreenPlacementStrategy;
291+
292+private:
293+ void add_surface(std::shared_ptr<ms::Surface> const&, ms::Session*) override {}
294+
295+ void remove_surface(std::weak_ptr<ms::Surface> const&, ms::Session*) override {}
296+
297+ void add_session(std::shared_ptr<ms::Session> const&) override {}
298+
299+ void remove_session(std::shared_ptr<ms::Session> const&) override {}
300+
301+ void add_display(Rectangle const&) override {}
302+
303+ void remove_display(Rectangle const&) override {}
304+
305+ void click(Point) override {}
306+
307+ void drag(Point) override {}
308+
309+ void resize(Point) override {}
310+
311+ void toggle_maximized() override {}
312+
313+ void toggle_max_horizontal() override {}
314+
315+ void toggle_max_vertical() override {}
316+
317+ int select_attribute_value(ms::Surface const&, MirSurfaceAttrib, int requested_value) override
318+ { return requested_value; }
319+
320+ void attribute_set(ms::Surface const&, MirSurfaceAttrib, int) override {}
321+};
322+
323+// simple tiling algorithm:
324+// o Switch apps: tap or click on the corresponding tile
325+// o Move window: Alt-leftmousebutton drag
326+// o Resize window: Alt-middle_button drag
327+// o Maximize/restore current window (to tile size): Alt-F11
328+// o Maximize/restore current window (to tile height): Shift-F11
329+// o Maximize/restore current window (to tile width): Ctrl-F11
330+// o client requests to maximize, vertically maximize & restore
331+class TilingWindowManager : public me::WindowManager
332+{
333+public:
334+ // We can't take the msh::FocusController directly as we create a WindowManager
335+ // in the_session_listener() and that is called when creating the focus controller
336+ using FocusControllerFactory = std::function<std::shared_ptr<msh::FocusController>()>;
337+
338+ explicit TilingWindowManager(FocusControllerFactory const& focus_controller) :
339+ focus_controller{focus_controller}
340+ {
341+ }
342+
343+ auto place(ms::Session const& session, ms::SurfaceCreationParameters const& request_parameters)
344+ -> ms::SurfaceCreationParameters override
345+ {
346+ auto parameters = request_parameters;
347+
348+ std::lock_guard<decltype(mutex)> lock(mutex);
349+ auto const ptile = session_info.find(&session);
350+ if (ptile != end(session_info))
351+ {
352+ Rectangle const& tile = ptile->second.tile;
353+ parameters.top_left = parameters.top_left + (tile.top_left - Point{0, 0});
354+
355+ clip_to_tile(parameters, tile);
356+ }
357+
358+ return parameters;
359+ }
360+
361+ void add_surface(
362+ std::shared_ptr<ms::Surface> const& surface,
363+ ms::Session* session) override
364+ {
365+ std::lock_guard<decltype(mutex)> lock(mutex);
366+ session_info[session].surfaces.push_back(surface);
367+ surface_info[surface].session = session_info[session].session;
368+ surface_info[surface].state = SurfaceInfo::restored;
369+ }
370+
371+ void remove_surface(
372+ std::weak_ptr<ms::Surface> const& surface,
373+ ms::Session* session) override
374+ {
375+ std::lock_guard<decltype(mutex)> lock(mutex);
376+ auto& surfaces = session_info[session].surfaces;
377+
378+ for (auto i = begin(surfaces); i != end(surfaces); ++i)
379+ {
380+ if (surface.lock() == i->lock())
381+ {
382+ surfaces.erase(i);
383+ break;
384+ }
385+ }
386+
387+ surface_info.erase(surface);
388+ }
389+
390+ void add_session(std::shared_ptr<ms::Session> const& session) override
391+ {
392+ std::lock_guard<decltype(mutex)> lock(mutex);
393+ session_info[session.get()] = session;
394+ update_tiles();
395+ }
396+
397+ void remove_session(std::shared_ptr<ms::Session> const& session) override
398+ {
399+ std::lock_guard<decltype(mutex)> lock(mutex);
400+ session_info.erase(session.get());
401+ update_tiles();
402+ }
403+
404+ void add_display(Rectangle const& area) override
405+ {
406+ std::lock_guard<decltype(mutex)> lock(mutex);
407+ displays.push_back(area);
408+ update_tiles();
409+ }
410+
411+ void remove_display(Rectangle const& area) override
412+ {
413+ std::lock_guard<decltype(mutex)> lock(mutex);
414+ auto const i = std::find(begin(displays), end(displays), area);
415+ if (i != end(displays)) displays.erase(i);
416+ update_tiles();
417+ }
418+
419+ void click(Point cursor) override
420+ {
421+ std::lock_guard<decltype(mutex)> lock(mutex);
422+
423+ if (auto const session = session_under(cursor))
424+ focus_controller()->set_focus_to(session);
425+
426+ old_cursor = cursor;
427+ }
428+
429+ void drag(Point cursor) override
430+ {
431+ std::lock_guard<decltype(mutex)> lock(mutex);
432+
433+ if (auto const session = session_under(cursor))
434+ {
435+ if (session == session_under(old_cursor))
436+ {
437+ auto const& info = session_info[session.get()];
438+
439+ if (drag(old_surface.lock(), cursor, old_cursor, info.tile))
440+ {
441+ // Still dragging the same old_surface
442+ }
443+ else if (drag(session->default_surface(), cursor, old_cursor, info.tile))
444+ {
445+ old_surface = session->default_surface();
446+ }
447+ else
448+ {
449+ for (auto const& ps : info.surfaces)
450+ {
451+ auto const new_surface = ps.lock();
452+
453+ if (drag(new_surface, cursor, old_cursor, info.tile))
454+ {
455+ old_surface = new_surface;
456+ break;
457+ }
458+ }
459+ }
460+ }
461+ }
462+
463+ old_cursor = cursor;
464+ }
465+
466+ void resize(Point cursor) override
467+ {
468+ std::lock_guard<decltype(mutex)> lock(mutex);
469+
470+ if (auto const session = session_under(cursor))
471+ {
472+ if (session == session_under(old_cursor))
473+ {
474+ auto const& info = session_info[session.get()];
475+
476+ if (resize(old_surface.lock(), cursor, old_cursor, info.tile))
477+ {
478+ // Still dragging the same old_surface
479+ }
480+ else if (resize(session->default_surface(), cursor, old_cursor, info.tile))
481+ {
482+ old_surface = session->default_surface();
483+ }
484+ else
485+ {
486+ for (auto const& ps : info.surfaces)
487+ {
488+ auto const new_surface = ps.lock();
489+
490+ if (resize(new_surface, cursor, old_cursor, info.tile))
491+ {
492+ old_surface = new_surface;
493+ break;
494+ }
495+ }
496+ }
497+ }
498+ }
499+
500+ old_cursor = cursor;
501+ }
502+
503+ void toggle_maximized() override
504+ {
505+ toggle(SurfaceInfo::maximized);
506+ }
507+
508+ void toggle_max_horizontal() override
509+ {
510+ toggle(SurfaceInfo::hmax);
511+ }
512+
513+ void toggle_max_vertical() override
514+ {
515+ toggle(SurfaceInfo::vmax);
516+ }
517+
518+ int select_attribute_value(ms::Surface const&, MirSurfaceAttrib, int requested_value) override
519+ { return requested_value; }
520+
521+ void attribute_set(ms::Surface const& surface, MirSurfaceAttrib attrib, int value) override
522+ {
523+ switch (attrib)
524+ {
525+ case mir_surface_attrib_state:
526+ {
527+ std::lock_guard<decltype(mutex)> lock(mutex);
528+ set_state(surface, value);
529+ break;
530+ }
531+ default:
532+ break;
533+ }
534+ }
535+
536+private:
537+ void update_tiles()
538+ {
539+ if (session_info.size() < 1 || displays.size() < 1) return;
540+
541+ auto const sessions = session_info.size();
542+ Rectangles view;
543+
544+ for (auto const& display : displays)
545+ view.add(display);
546+
547+ auto const bounding_rect = view.bounding_rectangle();
548+
549+ auto const total_width = bounding_rect.size.width.as_int();
550+ auto const total_height = bounding_rect.size.height.as_int();
551+
552+ auto index = 0;
553+
554+ for (auto& info : session_info)
555+ {
556+ auto const x = (total_width*index)/sessions;
557+ ++index;
558+ auto const dx = (total_width*index)/sessions - x;
559+
560+ auto const old_tile = info.second.tile;
561+ Rectangle const new_tile{{x, 0}, {dx, total_height}};
562+
563+ update_surfaces(info.first, old_tile, new_tile);
564+
565+ info.second.tile = new_tile;
566+ }
567+ }
568+
569+ void update_surfaces(ms::Session const* session, Rectangle const& old_tile, Rectangle const& new_tile)
570+ {
571+ auto displacement = new_tile.top_left - old_tile.top_left;
572+ auto& info = session_info[session];
573+
574+ for (auto const& ps : info.surfaces)
575+ {
576+ if (auto const surface = ps.lock())
577+ {
578+ auto const old_pos = surface->top_left();
579+ surface->move_to(old_pos + displacement);
580+
581+ fit_to_new_tile(*surface, old_tile, new_tile);
582+ }
583+ }
584+ }
585+
586+ static void clip_to_tile(ms::SurfaceCreationParameters& parameters, Rectangle const& tile)
587+ {
588+ auto const displacement = parameters.top_left - tile.top_left;
589+
590+ auto width = std::min(tile.size.width.as_int()-displacement.dx.as_int(), parameters.size.width.as_int());
591+ auto height = std::min(tile.size.height.as_int()-displacement.dy.as_int(), parameters.size.height.as_int());
592+
593+ parameters.size = Size{width, height};
594+ }
595+
596+ static void fit_to_new_tile(ms::Surface& surface, Rectangle const& old_tile, Rectangle const& new_tile)
597+ {
598+ auto const displacement = surface.top_left() - new_tile.top_left;
599+
600+ // For now just scale if was filling width/height of tile
601+ auto const old_size = surface.size();
602+ auto const scaled_width = old_size.width == old_tile.size.width ? new_tile.size.width : old_size.width;
603+ auto const scaled_height = old_size.height == old_tile.size.height ? new_tile.size.height : old_size.height;
604+
605+ auto width = std::min(new_tile.size.width.as_int()-displacement.dx.as_int(), scaled_width.as_int());
606+ auto height = std::min(new_tile.size.height.as_int()-displacement.dy.as_int(), scaled_height.as_int());
607+
608+ surface.resize({width, height});
609+ }
610+
611+ static bool drag(std::shared_ptr<ms::Surface> surface, Point to, Point from, Rectangle bounds)
612+ {
613+ if (surface && surface->input_area_contains(from))
614+ {
615+ auto const top_left = surface->top_left();
616+ auto const surface_size = surface->size();
617+ auto const bottom_right = top_left + as_displacement(surface_size);
618+
619+ auto movement = to - from;
620+
621+ if (movement.dx < DeltaX{0})
622+ movement.dx = std::max(movement.dx, (bounds.top_left - top_left).dx);
623+
624+ if (movement.dy < DeltaY{0})
625+ movement.dy = std::max(movement.dy, (bounds.top_left - top_left).dy);
626+
627+ if (movement.dx > DeltaX{0})
628+ movement.dx = std::min(movement.dx, (bounds.bottom_right() - bottom_right).dx);
629+
630+ if (movement.dy > DeltaY{0})
631+ movement.dy = std::min(movement.dy, (bounds.bottom_right() - bottom_right).dy);
632+
633+ auto new_pos = surface->top_left() + movement;
634+
635+ surface->move_to(new_pos);
636+ return true;
637+ }
638+
639+ return false;
640+ }
641+
642+ static bool resize(std::shared_ptr<ms::Surface> surface, Point cursor, Point old_cursor, Rectangle bounds)
643+ {
644+ if (surface && surface->input_area_contains(old_cursor))
645+ {
646+ auto const top_left = surface->top_left();
647+
648+ auto const old_displacement = old_cursor - top_left;
649+ auto const new_displacement = cursor - top_left;
650+
651+ auto const scale_x = new_displacement.dx.as_float()/std::max(1.0f, old_displacement.dx.as_float());
652+ auto const scale_y = new_displacement.dy.as_float()/std::max(1.0f, old_displacement.dy.as_float());
653+
654+ if (scale_x <= 0.0f || scale_y <= 0.0f) return false;
655+
656+ auto const old_size = surface->size();
657+ Size new_size{scale_x*old_size.width, scale_y*old_size.height};
658+
659+ auto const size_limits = as_size(bounds.bottom_right() - top_left);
660+
661+ if (new_size.width > size_limits.width)
662+ new_size.width = size_limits.width;
663+
664+ if (new_size.height > size_limits.height)
665+ new_size.height = size_limits.height;
666+
667+ surface->resize(new_size);
668+
669+ return true;
670+ }
671+
672+ return false;
673+ }
674+
675+ struct SurfaceInfo
676+ {
677+ SurfaceInfo() = default;
678+ std::weak_ptr<ms::Session> session;
679+ enum State { restored, maximized, hmax, vmax } state;
680+ Rectangle restore_rect;
681+ };
682+
683+ void toggle(SurfaceInfo::State state)
684+ {
685+ // We have to duplicate the state information into surface to notify the
686+ // client. But this has to happen without holding a lock (as we are also
687+ // notified via a call to attribute_set()).
688+ std::function<void()> notify_client = [](){};
689+
690+ if (auto const focussed_session = focus_controller()->focussed_application().lock())
691+ {
692+ if (auto const focussed_surface = focussed_session->default_surface())
693+ {
694+ std::lock_guard<decltype(mutex)> lock(mutex);
695+
696+ auto& surface_info = this->surface_info[focussed_surface];
697+
698+ if (surface_info.state == state)
699+ {
700+ notify_client = [focussed_surface]()
701+ {
702+ focussed_surface->configure(
703+ mir_surface_attrib_state,
704+ mir_surface_state_restored);
705+ };
706+ }
707+ else
708+ {
709+ switch (state)
710+ {
711+ case SurfaceInfo::maximized:
712+ notify_client = [focussed_surface]()
713+ {
714+ focussed_surface->configure(
715+ mir_surface_attrib_state,
716+ mir_surface_state_maximized);
717+ };
718+ break;
719+
720+ case SurfaceInfo::hmax:
721+ {
722+ // We have to implement this here as we can't code hmax into mir_surface_attrib_state
723+ auto const& session_info = this->session_info[focussed_session.get()];
724+ surface_info.state = state;
725+ focussed_surface->move_to({session_info.tile.top_left.x, surface_info.restore_rect.top_left.y});
726+ focussed_surface->resize({session_info.tile.size.width, surface_info.restore_rect.size.height});
727+
728+ notify_client = [focussed_surface]()
729+ {
730+ focussed_surface->configure(
731+ mir_surface_attrib_state,
732+ mir_surface_state_unknown);
733+ };
734+ break;
735+ }
736+ case SurfaceInfo::vmax:
737+ notify_client = [focussed_surface]()
738+ {
739+ focussed_surface->configure(
740+ mir_surface_attrib_state,
741+ mir_surface_state_vertmaximized);
742+ };
743+ break;
744+
745+ default:
746+ break;
747+ }
748+ }
749+ }
750+ }
751+
752+ notify_client();
753+ }
754+
755+ void set_state(ms::Surface const& surface, int value)
756+ {
757+ auto new_state = SurfaceInfo::restored;
758+
759+ switch (value)
760+ {
761+ case mir_surface_state_restored:
762+ new_state = SurfaceInfo::restored;
763+ break;
764+
765+ case mir_surface_state_maximized:
766+ new_state = SurfaceInfo::maximized;
767+ break;
768+
769+ case mir_surface_state_vertmaximized:
770+ new_state = SurfaceInfo::vmax;
771+ break;
772+
773+ default:
774+ return;
775+ }
776+
777+ for (auto& i : surface_info)
778+ {
779+ if (auto const sp = i.first.lock())
780+ {
781+ if (sp.get() == &surface)
782+ {
783+ auto& surface_info = i.second;
784+
785+ if (surface_info.state == SurfaceInfo::restored)
786+ {
787+ surface_info.restore_rect = {sp->top_left(), sp->size()};
788+ }
789+
790+ if (surface_info.state == new_state)
791+ {
792+ return; // Nothing to do
793+ }
794+
795+ auto const& session_info =
796+ this->session_info[surface_info.session.lock().get()];
797+
798+ switch (new_state)
799+ {
800+ case SurfaceInfo::restored:
801+ sp->move_to(surface_info.restore_rect.top_left);
802+ sp->resize(surface_info.restore_rect.size);
803+ break;
804+
805+ case SurfaceInfo::maximized:
806+ sp->move_to(session_info.tile.top_left);
807+ sp->resize(session_info.tile.size);
808+ break;
809+
810+ case SurfaceInfo::hmax:
811+ sp->move_to({session_info.tile.top_left.x, surface_info.restore_rect.top_left.y});
812+ sp->resize({session_info.tile.size.width, surface_info.restore_rect.size.height});
813+ break;
814+
815+ case SurfaceInfo::vmax:
816+ sp->move_to({surface_info.restore_rect.top_left.x, session_info.tile.top_left.y});
817+ sp->resize({surface_info.restore_rect.size.width, session_info.tile.size.height});
818+ break;
819+ }
820+
821+ surface_info.state = new_state;
822+ }
823+ }
824+ }
825+ }
826+
827+ std::shared_ptr<ms::Session> session_under(Point position)
828+ {
829+ for(auto& info : session_info)
830+ {
831+ if (info.second.tile.contains(position))
832+ {
833+ return info.second.session.lock();
834+ }
835+ }
836+
837+ return std::shared_ptr<ms::Session>{};
838+ }
839+
840+ struct SessionInfo
841+ {
842+ SessionInfo() = default;
843+ SessionInfo& operator=(std::weak_ptr<ms::Session> const& session)
844+ {
845+ this->session = session;
846+ surfaces.clear();
847+ return *this;
848+ }
849+ std::weak_ptr<ms::Session> session;
850+ Rectangle tile;
851+ std::vector<std::weak_ptr<ms::Surface>> surfaces;
852+ };
853+
854+ FocusControllerFactory const focus_controller;
855+
856+ std::mutex mutex;
857+ std::vector<Rectangle> displays;
858+
859+ std::map<ms::Session const*, SessionInfo> session_info;
860+ std::map<std::weak_ptr<ms::Surface>, SurfaceInfo, std::owner_less<std::weak_ptr<ms::Surface>>> surface_info;
861+
862+ Point old_cursor{};
863+ std::weak_ptr<ms::Surface> old_surface;
864+};
865+}
866+
867+class me::EventTracker : public mi::EventFilter
868+{
869+public:
870+ explicit EventTracker(std::shared_ptr<me::WindowManager> const& window_manager) :
871+ window_manager{window_manager} {}
872+
873+ bool handle(MirEvent const& event) override
874+ {
875+ switch (event.type)
876+ {
877+ case mir_event_type_key:
878+ return handle_key_event(event.key);
879+
880+ case mir_event_type_motion:
881+ return handle_motion_event(event.motion);
882+
883+ default:
884+ return false;
885+ }
886+ }
887+
888+private:
889+ bool handle_key_event(MirKeyEvent const& event)
890+ {
891+ static const int modifier_mask =
892+ mir_key_modifier_alt |
893+ mir_key_modifier_shift |
894+ mir_key_modifier_sym |
895+ mir_key_modifier_ctrl |
896+ mir_key_modifier_meta;
897+
898+ if (event.action == mir_key_action_down &&
899+ event.scan_code == KEY_F11)
900+ {
901+ if (auto const wm = window_manager.lock())
902+ switch (event.modifiers & modifier_mask)
903+ {
904+ case mir_key_modifier_alt:
905+ wm->toggle_maximized();
906+ return true;
907+
908+ case mir_key_modifier_shift:
909+ wm->toggle_max_vertical();
910+ return true;
911+
912+ case mir_key_modifier_ctrl:
913+ wm->toggle_max_horizontal();
914+ return true;
915+
916+ default:
917+ break;
918+ }
919+ }
920+
921+ return false;
922+ }
923+
924+ bool handle_motion_event(MirMotionEvent const& event)
925+ {
926+ if (event.action == mir_motion_action_down ||
927+ event.action == mir_motion_action_pointer_down)
928+ {
929+ if (auto const wm = window_manager.lock())
930+ {
931+ wm->click(average_pointer(event.pointer_count, event.pointer_coordinates));
932+ return false;
933+ }
934+ }
935+ else if (event.action == mir_motion_action_move &&
936+ event.modifiers & mir_key_modifier_alt)
937+ {
938+ if (auto const wm = window_manager.lock())
939+ {
940+ switch (event.button_state)
941+ {
942+ case mir_motion_button_primary:
943+ wm->drag(average_pointer(event.pointer_count, event.pointer_coordinates));
944+ return true;
945+
946+ case mir_motion_button_tertiary:
947+ wm->resize(average_pointer(event.pointer_count, event.pointer_coordinates));
948+ return true;
949+
950+ default:
951+ ;// ignore
952+ }
953+ }
954+ }
955+
956+ return false;
957+ }
958+
959+ static Point average_pointer(size_t pointer_count, MirMotionPointer const* pointer_coordinates)
960+ {
961+ long total_x = 0;
962+ long total_y = 0;
963+
964+ for (auto p = pointer_coordinates; p != pointer_coordinates + pointer_count; ++p)
965+ {
966+ total_x += p->x;
967+ total_y += p->y;
968+ }
969+
970+ return Point{total_x/pointer_count, total_y/pointer_count};
971+ }
972+
973+ std::weak_ptr<me::WindowManager> const window_manager;
974+};
975+
976+auto me::WindowManagmentFactory::window_manager() -> std::shared_ptr<me::WindowManager>
977+{
978+ auto tmp = wm.lock();
979+
980+ if (!tmp)
981+ {
982+ auto const options = server.get_options();
983+ auto const selection = options->get<std::string>(wm_option);
984+
985+ if (selection == wm_tiling)
986+ {
987+ auto focus_controller_factory = [this] { return server.the_focus_controller(); };
988+ tmp = std::make_shared<TilingWindowManager>(focus_controller_factory);
989+ }
990+ else if (selection == wm_fullscreen)
991+ tmp = std::make_shared<FullscreenWindowManager>(server.the_shell_display_layout());
992+ else
993+ throw mir::AbnormalExit("Unknown window manager: " + selection);
994+
995+ et = std::make_shared<EventTracker>(tmp);
996+ server.the_composite_event_filter()->prepend(et);
997+ wm = tmp;
998+ }
999+
1000+ return tmp;
1001+}
1002
1003=== added file 'examples/server_example_window_management.h'
1004--- examples/server_example_window_management.h 1970-01-01 00:00:00 +0000
1005+++ examples/server_example_window_management.h 2015-01-14 12:09:57 +0000
1006@@ -0,0 +1,95 @@
1007+/*
1008+ * Copyright © 2014 Canonical Ltd.
1009+ *
1010+ * This program is free software: you can redistribute it and/or modify it
1011+ * under the terms of the GNU General Public License version 3,
1012+ * as published by the Free Software Foundation.
1013+ *
1014+ * This program is distributed in the hope that it will be useful,
1015+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1016+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1017+ * GNU General Public License for more details.
1018+ *
1019+ * You should have received a copy of the GNU General Public License
1020+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1021+ *
1022+ * Authored By: Alan Griffiths <alan@octopull.co.uk>
1023+ */
1024+
1025+#ifndef MIR_EXAMPLES_WINDOW_MANAGEMENT_H_
1026+#define MIR_EXAMPLES_WINDOW_MANAGEMENT_H_
1027+
1028+#include "mir/geometry/rectangles.h"
1029+#include "mir/scene/session.h"
1030+#include "mir/scene/surface.h"
1031+#include "mir/scene/placement_strategy.h"
1032+#include "mir/scene/surface_configurator.h"
1033+#include "mir/scene/surface_creation_parameters.h"
1034+
1035+#include <memory>
1036+
1037+///\example server_example_window_management.h
1038+/// Demonstrate simple window management strategies
1039+
1040+namespace mir
1041+{
1042+class Server;
1043+
1044+namespace examples
1045+{
1046+class WindowManager :
1047+ public virtual scene::PlacementStrategy,
1048+ public virtual scene::SurfaceConfigurator
1049+{
1050+public:
1051+
1052+ virtual void add_surface(
1053+ std::shared_ptr<scene::Surface> const& surface,
1054+ scene::Session* session) = 0;
1055+
1056+ virtual void remove_surface(
1057+ std::weak_ptr<scene::Surface> const& surface,
1058+ scene::Session* session) = 0;
1059+
1060+ virtual void add_session(std::shared_ptr<scene::Session> const& session) = 0;
1061+
1062+ virtual void remove_session(std::shared_ptr<scene::Session> const& session) = 0;
1063+
1064+ virtual void add_display(geometry::Rectangle const& area) = 0;
1065+
1066+ virtual void remove_display(geometry::Rectangle const& area) = 0;
1067+
1068+ virtual void click(geometry::Point cursor) = 0;
1069+
1070+ virtual void drag(geometry::Point cursor) = 0;
1071+
1072+ virtual void resize(geometry::Point cursor) = 0;
1073+
1074+ virtual void toggle_maximized() = 0;
1075+
1076+ virtual void toggle_max_horizontal() = 0;
1077+
1078+ virtual void toggle_max_vertical() = 0;
1079+};
1080+
1081+class EventTracker;
1082+
1083+class WindowManagmentFactory
1084+{
1085+public:
1086+ explicit WindowManagmentFactory(Server& server) : server{server} {}
1087+
1088+ auto window_manager() -> std::shared_ptr<WindowManager>;
1089+
1090+private:
1091+ Server& server;
1092+ std::weak_ptr<WindowManager> wm;
1093+ std::shared_ptr<EventTracker> et;
1094+};
1095+
1096+extern char const* const wm_option;
1097+extern char const* const wm_description;
1098+}
1099+}
1100+
1101+#endif
1102
1103=== modified file 'examples/server_example_window_manager.cpp'
1104--- examples/server_example_window_manager.cpp 2015-01-05 14:50:58 +0000
1105+++ examples/server_example_window_manager.cpp 2015-01-14 12:09:57 +0000
1106@@ -17,317 +17,60 @@
1107 */
1108
1109 #include "server_example_window_manager.h"
1110-#include "server_example_fullscreen_placement_strategy.h"
1111+#include "server_example_window_management.h"
1112
1113-#include "mir/abnormal_exit.h"
1114 #include "mir/server.h"
1115 #include "mir/compositor/display_buffer_compositor_factory.h"
1116 #include "mir/compositor/display_buffer_compositor.h"
1117-#include "mir/geometry/rectangles.h"
1118-#include "mir/geometry/displacement.h"
1119 #include "mir/graphics/display_buffer.h"
1120 #include "mir/options/option.h"
1121-#include "mir/scene/observer.h"
1122-#include "mir/scene/session.h"
1123-#include "mir/scene/surface.h"
1124-#include "mir/scene/placement_strategy.h"
1125-#include "mir/scene/surface_creation_parameters.h"
1126-#include "mir/shell/session_coordinator_wrapper.h"
1127-#include "mir/shell/surface_coordinator_wrapper.h"
1128-
1129-#include <algorithm>
1130-#include <map>
1131-#include <mutex>
1132+#include "mir/scene/session_listener.h"
1133
1134 namespace mc = mir::compositor;
1135 namespace me = mir::examples;
1136-namespace mf = mir::frontend;
1137 namespace mg = mir::graphics;
1138 namespace ms = mir::scene;
1139-namespace msh = mir::shell;
1140 using namespace mir::geometry;
1141
1142 ///\example server_example_window_manager.cpp
1143-/// Demonstrate a simple window management strategy
1144+/// Demonstrate introducing a window management strategy
1145
1146 namespace
1147 {
1148-class WindowManager : public virtual ms::PlacementStrategy
1149-{
1150-public:
1151-
1152- virtual void add_surface(
1153- std::shared_ptr<ms::Surface> const& surface,
1154- ms::Session* session) = 0;
1155-
1156- virtual void remove_surface(std::weak_ptr<ms::Surface> const& surface) = 0;
1157-
1158- virtual void add_session(std::shared_ptr<mf::Session> const& session) = 0;
1159-
1160- virtual void remove_session(std::shared_ptr<mf::Session> const& session) = 0;
1161-
1162- virtual void add_display(Rectangle const& area) = 0;
1163-
1164- virtual void remove_display(Rectangle const& area) = 0;
1165-};
1166-
1167-class FullscreenWindowManager : public WindowManager, me::FullscreenPlacementStrategy
1168-{
1169-public:
1170- using me::FullscreenPlacementStrategy::FullscreenPlacementStrategy;
1171-
1172-private:
1173- void add_surface(std::shared_ptr<ms::Surface> const&, ms::Session*) override {}
1174-
1175- void remove_surface(std::weak_ptr<ms::Surface> const&) override {}
1176-
1177- void add_session(std::shared_ptr<mf::Session> const&) override {}
1178-
1179- void remove_session(std::shared_ptr<mf::Session> const&) override {}
1180-
1181- void add_display(Rectangle const&) override {}
1182-
1183- void remove_display(Rectangle const&) override {}
1184-};
1185-
1186-class TilingWindowManager : public WindowManager
1187-{
1188- auto place(ms::Session const& session, ms::SurfaceCreationParameters const& request_parameters)
1189- -> ms::SurfaceCreationParameters override
1190- {
1191- auto parameters = request_parameters;
1192-
1193- std::lock_guard<decltype(mutex)> lock(mutex);
1194- auto const ptile = tiles.find(&session);
1195- if (ptile != end(tiles))
1196- {
1197- Rectangle const& tile = ptile->second;
1198- parameters.top_left = parameters.top_left + (tile.top_left - Point{0, 0});
1199-
1200- clip_to_tile(parameters, tile);
1201- }
1202-
1203- return parameters;
1204- }
1205-
1206- void add_surface(
1207- std::shared_ptr<ms::Surface> const& surface,
1208- ms::Session* session)
1209- {
1210- std::lock_guard<decltype(mutex)> lock(mutex);
1211- surfaces.emplace(session, surface);
1212- }
1213-
1214- void remove_surface(std::weak_ptr<ms::Surface> const& /*surface*/)
1215- {
1216- // This looks odd but we want to block in case we're using the surface
1217- std::lock_guard<decltype(mutex)> lock(mutex);
1218- }
1219-
1220- void add_session(std::shared_ptr<mf::Session> const& session)
1221- {
1222- std::lock_guard<decltype(mutex)> lock(mutex);
1223- tiles[session.get()] = Rectangle{};
1224- update_tiles();
1225- }
1226-
1227- void remove_session(std::shared_ptr<mf::Session> const& session)
1228- {
1229- std::lock_guard<decltype(mutex)> lock(mutex);
1230- tiles.erase(session.get());
1231- surfaces.erase(session.get());
1232- update_tiles();
1233- }
1234-
1235- void add_display(Rectangle const& area)
1236- {
1237- std::lock_guard<decltype(mutex)> lock(mutex);
1238- displays.push_back(area);
1239- update_tiles();
1240- }
1241-
1242- void remove_display(Rectangle const& area)
1243- {
1244- std::lock_guard<decltype(mutex)> lock(mutex);
1245- auto const i = std::find(begin(displays), end(displays), area);
1246- if (i != end(displays)) displays.erase(i);
1247- update_tiles();
1248- }
1249-
1250-private:
1251- void update_tiles()
1252- {
1253- if (tiles.size() < 1 || displays.size() < 1) return;
1254-
1255- auto const sessions = tiles.size();
1256- Rectangles view;
1257-
1258- for (auto const& display : displays)
1259- view.add(display);
1260-
1261- auto const bounding_rect = view.bounding_rectangle();
1262-
1263- auto const total_width = bounding_rect.size.width.as_int();
1264- auto const total_height = bounding_rect.size.height.as_int();
1265-
1266- auto index = 0;
1267-
1268- for (auto& tile : tiles)
1269- {
1270- auto const x = (total_width*index)/sessions;
1271- ++index;
1272- auto const dx = (total_width*index)/sessions - x;
1273-
1274- auto const old_tile = tile.second;
1275- Rectangle const new_tile{{x, 0}, {dx, total_height}};
1276-
1277- update_surfaces(tile.first, old_tile, new_tile);
1278-
1279- tile.second = new_tile;
1280- }
1281- }
1282-
1283- void update_surfaces(mf::Session const* session, Rectangle const& old_tile, Rectangle const& new_tile)
1284- {
1285- auto displacement = new_tile.top_left - old_tile.top_left;
1286- auto const moved = surfaces.equal_range(session);
1287-
1288- for (auto p = moved.first; p != moved.second; ++p)
1289- {
1290- if (auto const surface = p->second.lock())
1291- {
1292- auto const old_pos = surface->top_left();
1293- surface->move_to(old_pos + displacement);
1294-
1295- fit_to_new_tile(*surface, old_tile, new_tile);
1296- }
1297- }
1298- }
1299-
1300- static void clip_to_tile(ms::SurfaceCreationParameters& parameters, Rectangle const& tile)
1301- {
1302- auto const displacement = parameters.top_left - tile.top_left;
1303-
1304- auto width = std::min(tile.size.width.as_int()-displacement.dx.as_int(), parameters.size.width.as_int());
1305- auto height = std::min(tile.size.height.as_int()-displacement.dy.as_int(), parameters.size.height.as_int());
1306-
1307- parameters.size = Size{width, height};
1308- }
1309-
1310- static void fit_to_new_tile(ms::Surface& surface, Rectangle const& old_tile, Rectangle const& new_tile)
1311- {
1312- auto const displacement = surface.top_left() - new_tile.top_left;
1313-
1314- // For now just scale if was filling width/height of tile
1315- auto const old_size = surface.size();
1316- auto const scaled_width = old_size.width == old_tile.size.width ? new_tile.size.width : old_size.width;
1317- auto const scaled_height = old_size.height == old_tile.size.height ? new_tile.size.height : old_size.height;
1318-
1319- auto width = std::min(new_tile.size.width.as_int()-displacement.dx.as_int(), scaled_width.as_int());
1320- auto height = std::min(new_tile.size.height.as_int()-displacement.dy.as_int(), scaled_height.as_int());
1321-
1322- surface.resize({width, height});
1323- }
1324-
1325- std::mutex mutex;
1326- std::vector<Rectangle> displays;
1327- std::map<mf::Session const*, Rectangle> tiles;
1328- std::multimap<mf::Session const*, std::weak_ptr<ms::Surface>> surfaces;
1329-};
1330-
1331-auto const option = "window-manager";
1332-auto const description = "window management strategy [{tiling|fullscreen}]";
1333-auto const tiling = "tiling";
1334-auto const fullscreen = "fullscreen";
1335-
1336-class WindowManagmentFactory
1337-{
1338-public:
1339- explicit WindowManagmentFactory(mir::Server& server) : server{server} {}
1340-
1341- auto window_manager() -> std::shared_ptr<WindowManager>
1342- {
1343- auto tmp = wm.lock();
1344-
1345- if (!tmp)
1346- {
1347- auto const options = server.get_options();
1348- auto const selection = options->get<std::string>(option);
1349-
1350- if (selection == tiling)
1351- tmp = std::make_shared<TilingWindowManager>();
1352- else if (selection == fullscreen)
1353- tmp = std::make_shared<FullscreenWindowManager>(server.the_shell_display_layout());
1354- else
1355- throw mir::AbnormalExit("Unknown window manager: " + selection);
1356-
1357- wm = tmp;
1358- }
1359-
1360- return tmp;
1361- }
1362-
1363-private:
1364- mir::Server& server;
1365- std::weak_ptr<WindowManager> wm;
1366-};
1367-
1368-class SessionTracker : public msh::SessionCoordinatorWrapper
1369-{
1370-public:
1371- SessionTracker(
1372- std::shared_ptr<ms::SessionCoordinator> const& wrapped,
1373- std::shared_ptr<WindowManager> const& window_manager) :
1374- msh::SessionCoordinatorWrapper(wrapped),
1375+class SceneTracker : public ms::SessionListener
1376+{
1377+public:
1378+ SceneTracker(std::shared_ptr<me::WindowManager> const& window_manager) :
1379 window_manager(window_manager)
1380 {
1381 }
1382
1383 private:
1384- auto open_session(pid_t client_pid, std::string const& name, std::shared_ptr<mf::EventSink> const& sink)
1385- -> std::shared_ptr<mf::Session> override
1386+
1387+ void starting(std::shared_ptr<ms::Session> const& session) override
1388 {
1389- auto const new_session = msh::SessionCoordinatorWrapper::open_session(client_pid, name, sink);
1390- window_manager->add_session(new_session);
1391- return new_session;
1392+ window_manager->add_session(session);
1393 }
1394
1395- void close_session(std::shared_ptr<mf::Session> const& session) override
1396+ void stopping(std::shared_ptr<ms::Session> const& session) override
1397 {
1398 window_manager->remove_session(session);
1399- msh::SessionCoordinatorWrapper::close_session(session);
1400- }
1401-
1402- std::shared_ptr<WindowManager> const window_manager;
1403-};
1404-
1405-class SurfaceTracker : public msh::SurfaceCoordinatorWrapper
1406-{
1407-public:
1408- SurfaceTracker(
1409- std::shared_ptr<ms::SurfaceCoordinator> const& wrapped,
1410- std::shared_ptr<WindowManager> const& window_manager) :
1411- msh::SurfaceCoordinatorWrapper(wrapped),
1412- window_manager(window_manager)
1413- {
1414- }
1415-
1416-private:
1417- auto add_surface(ms::SurfaceCreationParameters const& params, ms::Session* session)
1418- -> std::shared_ptr<ms::Surface> override
1419- {
1420- auto const new_surface = msh::SurfaceCoordinatorWrapper::add_surface(params, session);
1421- window_manager->add_surface(new_surface, session);
1422- return new_surface;
1423- }
1424-
1425- void remove_surface(std::weak_ptr<ms::Surface> const& surface)
1426- {
1427- window_manager->remove_surface(surface);
1428- msh::SurfaceCoordinatorWrapper::remove_surface(surface);
1429- }
1430-
1431- std::shared_ptr<WindowManager> const window_manager;
1432+ }
1433+
1434+ void focused(std::shared_ptr<ms::Session> const& /*session*/) override {}
1435+ void unfocused() override {}
1436+
1437+ void surface_created(ms::Session& session, std::shared_ptr<ms::Surface> const& surface) override
1438+ {
1439+ window_manager->add_surface(surface, &session);
1440+ }
1441+
1442+ void destroying_surface(ms::Session& session, std::shared_ptr<ms::Surface> const& surface) override
1443+ {
1444+ window_manager->remove_surface(surface, &session);
1445+ }
1446+
1447+ std::shared_ptr<me::WindowManager> const window_manager;
1448 };
1449
1450 class DisplayTracker : public mc::DisplayBufferCompositor
1451@@ -336,7 +79,7 @@
1452 DisplayTracker(
1453 std::unique_ptr<mc::DisplayBufferCompositor>&& wrapped,
1454 Rectangle const& area,
1455- std::shared_ptr<WindowManager> const& window_manager) :
1456+ std::shared_ptr<me::WindowManager> const& window_manager) :
1457 wrapped{std::move(wrapped)},
1458 area{area},
1459 window_manager(window_manager)
1460@@ -358,7 +101,7 @@
1461
1462 std::unique_ptr<mc::DisplayBufferCompositor> const wrapped;
1463 Rectangle const area;
1464- std::shared_ptr<WindowManager> const window_manager;
1465+ std::shared_ptr<me::WindowManager> const window_manager;
1466 };
1467
1468 class DisplayTrackerFactory : public mc::DisplayBufferCompositorFactory
1469@@ -366,7 +109,7 @@
1470 public:
1471 DisplayTrackerFactory(
1472 std::shared_ptr<mc::DisplayBufferCompositorFactory> const& wrapped,
1473- std::shared_ptr<WindowManager> const& window_manager) :
1474+ std::shared_ptr<me::WindowManager> const& window_manager) :
1475 wrapped{wrapped},
1476 window_manager(window_manager)
1477 {
1478@@ -381,49 +124,47 @@
1479 }
1480
1481 std::shared_ptr<mc::DisplayBufferCompositorFactory> const wrapped;
1482- std::shared_ptr<WindowManager> const window_manager;
1483+ std::shared_ptr<me::WindowManager> const window_manager;
1484 };
1485 }
1486
1487 void me::add_window_manager_option_to(Server& server)
1488 {
1489- server.add_configuration_option(option, description, mir::OptionType::string);
1490+ server.add_configuration_option(me::wm_option, me::wm_description, mir::OptionType::string);
1491
1492- auto const factory = std::make_shared<WindowManagmentFactory>(server);
1493+ auto const factory = std::make_shared<me::WindowManagmentFactory>(server);
1494
1495 server.override_the_placement_strategy([factory, &server]()
1496 -> std::shared_ptr<ms::PlacementStrategy>
1497 {
1498 auto const options = server.get_options();
1499
1500- if (!options->is_set(option))
1501+ if (!options->is_set(me::wm_option))
1502 return std::shared_ptr<ms::PlacementStrategy>{};
1503
1504 return factory->window_manager();
1505 });
1506
1507- server.wrap_session_coordinator([factory, &server]
1508- (std::shared_ptr<ms::SessionCoordinator> const& wrapped)
1509- -> std::shared_ptr<ms::SessionCoordinator>
1510+ server.override_the_session_listener([factory, &server]()
1511+ -> std::shared_ptr<ms::SessionListener>
1512 {
1513 auto const options = server.get_options();
1514
1515- if (!options->is_set(option))
1516- return wrapped;
1517+ if (!options->is_set(me::wm_option))
1518+ return std::shared_ptr<ms::SessionListener>{};
1519
1520- return std::make_shared<SessionTracker>(wrapped, factory->window_manager());
1521+ return std::make_shared<SceneTracker>(factory->window_manager());
1522 });
1523
1524- server.wrap_surface_coordinator([factory, &server]
1525- (std::shared_ptr<ms::SurfaceCoordinator> const& wrapped)
1526- -> std::shared_ptr<ms::SurfaceCoordinator>
1527+ server.override_the_surface_configurator([factory, &server]()
1528+ -> std::shared_ptr<ms::SurfaceConfigurator>
1529 {
1530 auto const options = server.get_options();
1531
1532- if (!options->is_set(option))
1533- return wrapped;
1534+ if (!options->is_set(me::wm_option))
1535+ return std::shared_ptr<ms::SurfaceConfigurator>{};
1536
1537- return std::make_shared<SurfaceTracker>(wrapped, factory->window_manager());
1538+ return factory->window_manager();
1539 });
1540
1541 server.wrap_display_buffer_compositor_factory([factory, &server]
1542@@ -432,7 +173,7 @@
1543 {
1544 auto const options = server.get_options();
1545
1546- if (!options->is_set(option))
1547+ if (!options->is_set(me::wm_option))
1548 return wrapped;
1549
1550 return std::make_shared<DisplayTrackerFactory>(wrapped, factory->window_manager());

Subscribers

People subscribed via source and target branches