45
37
using namespace mir::geometry;
47
39
///\example server_example_window_management.cpp
48
/// Demonstrate simple window management strategies
50
char const* const me::wm_option = "window-manager";
51
char const* const me::wm_description = "window management strategy [{tiling|fullscreen}]";
40
/// Demonstrate introducing a window management strategy
44
char const* const wm_option = "window-manager";
45
char const* const wm_description = "window management strategy [{tiling|fullscreen|canonical}]";
55
47
char const* const wm_tiling = "tiling";
56
48
char const* const wm_fullscreen = "fullscreen";
49
char const* const wm_canonical = "canonical";
51
struct NullSessionInfo
55
struct NullSurfaceInfo
58
std::shared_ptr<ms::Session> const& /*session*/,
59
std::shared_ptr<ms::Surface> const& /*surface*/,
60
ms::SurfaceCreationParameters const& /*params*/) {}
58
64
// Very simple - make every surface fullscreen
59
class FullscreenWindowManager : public me::WindowManager, me::FullscreenPlacementStrategy
62
using me::FullscreenPlacementStrategy::FullscreenPlacementStrategy;
65
void add_surface(std::shared_ptr<ms::Surface> const&, ms::Session*) override {}
67
void remove_surface(std::weak_ptr<ms::Surface> const&, ms::Session*) override {}
69
void add_session(std::shared_ptr<ms::Session> const&) override {}
71
void remove_session(std::shared_ptr<ms::Session> const&) override {}
73
void add_display(Rectangle const&) override {}
75
void remove_display(Rectangle const&) override {}
77
void click(Point) override {}
79
void drag(Point) override {}
81
void resize(Point) override {}
83
void toggle(MirSurfaceState) override {}
85
int select_attribute_value(ms::Surface const&, MirSurfaceAttrib, int requested_value) override
86
{ return requested_value; }
88
void attribute_set(ms::Surface const&, MirSurfaceAttrib, int) override {}
91
// simple tiling algorithm:
92
// o Switch apps: tap or click on the corresponding tile
93
// o Move window: Alt-leftmousebutton drag
94
// o Resize window: Alt-middle_button drag
95
// o Maximize/restore current window (to tile size): Alt-F11
96
// o Maximize/restore current window (to tile height): Shift-F11
97
// o Maximize/restore current window (to tile width): Ctrl-F11
98
// o client requests to maximize, vertically maximize & restore
99
class TilingWindowManager : public me::WindowManager
102
// We can't take the msh::FocusController directly as we create a WindowManager
103
// in the_session_listener() and that is called when creating the focus controller
104
using FocusControllerFactory = std::function<std::shared_ptr<msh::FocusController>()>;
106
explicit TilingWindowManager(FocusControllerFactory const& focus_controller) :
107
focus_controller{focus_controller}
111
auto place(ms::Session const& session, ms::SurfaceCreationParameters const& request_parameters)
112
-> ms::SurfaceCreationParameters override
114
auto parameters = request_parameters;
116
std::lock_guard<decltype(mutex)> lock(mutex);
117
auto const ptile = session_info.find(&session);
118
if (ptile != end(session_info))
120
Rectangle const& tile = ptile->second.tile;
121
parameters.top_left = parameters.top_left + (tile.top_left - Point{0, 0});
123
clip_to_tile(parameters, tile);
130
std::shared_ptr<ms::Surface> const& surface,
131
ms::Session* session) override
133
std::lock_guard<decltype(mutex)> lock(mutex);
134
session_info[session].surfaces.push_back(surface);
135
surface_info[surface].session = session_info[session].session;
136
surface_info[surface].state = mir_surface_state_restored;
140
std::weak_ptr<ms::Surface> const& surface,
141
ms::Session* session) override
143
std::lock_guard<decltype(mutex)> lock(mutex);
144
auto& surfaces = session_info[session].surfaces;
146
for (auto i = begin(surfaces); i != end(surfaces); ++i)
148
if (surface.lock() == i->lock())
155
surface_info.erase(surface);
158
void add_session(std::shared_ptr<ms::Session> const& session) override
160
std::lock_guard<decltype(mutex)> lock(mutex);
161
session_info[session.get()] = session;
165
void remove_session(std::shared_ptr<ms::Session> const& session) override
167
std::lock_guard<decltype(mutex)> lock(mutex);
168
session_info.erase(session.get());
172
void add_display(Rectangle const& area) override
174
std::lock_guard<decltype(mutex)> lock(mutex);
179
void remove_display(Rectangle const& area) override
181
std::lock_guard<decltype(mutex)> lock(mutex);
182
displays.remove(area);
186
void click(Point cursor) override
188
std::lock_guard<decltype(mutex)> lock(mutex);
190
if (auto const session = session_under(cursor))
191
focus_controller()->set_focus_to(session);
196
void drag(Point cursor) override
198
std::lock_guard<decltype(mutex)> lock(mutex);
200
if (auto const session = session_under(cursor))
202
if (session == session_under(old_cursor))
204
auto const& info = session_info[session.get()];
206
if (drag(old_surface.lock(), cursor, old_cursor, info.tile))
208
// Still dragging the same old_surface
210
else if (drag(session->default_surface(), cursor, old_cursor, info.tile))
212
old_surface = session->default_surface();
216
for (auto const& ps : info.surfaces)
218
auto const new_surface = ps.lock();
220
if (drag(new_surface, cursor, old_cursor, info.tile))
222
old_surface = new_surface;
233
void resize(Point cursor) override
235
std::lock_guard<decltype(mutex)> lock(mutex);
237
if (auto const session = session_under(cursor))
239
if (session == session_under(old_cursor))
241
auto const& info = session_info[session.get()];
243
if (resize(old_surface.lock(), cursor, old_cursor, info.tile))
245
// Still dragging the same old_surface
247
else if (resize(session->default_surface(), cursor, old_cursor, info.tile))
249
old_surface = session->default_surface();
253
for (auto const& ps : info.surfaces)
255
auto const new_surface = ps.lock();
257
if (resize(new_surface, cursor, old_cursor, info.tile))
259
old_surface = new_surface;
270
void toggle(MirSurfaceState state) override
272
if (auto const focussed_session = focus_controller()->focussed_application().lock())
274
if (auto const focussed_surface = focussed_session->default_surface())
277
std::lock_guard<decltype(mutex)> lock(mutex);
279
if (surface_info[focussed_surface].state == state)
280
state = mir_surface_state_restored;
283
focussed_surface->configure(mir_surface_attrib_state, state);
284
attribute_set(*focussed_surface, mir_surface_attrib_state, state);
289
int select_attribute_value(ms::Surface const&, MirSurfaceAttrib, int requested_value) override
290
{ return requested_value; }
292
void attribute_set(ms::Surface const& surface, MirSurfaceAttrib attrib, int value) override
296
case mir_surface_attrib_state:
298
std::lock_guard<decltype(mutex)> lock(mutex);
299
set_state(surface, value);
310
if (session_info.size() < 1 || displays.size() < 1) return;
312
auto const sessions = session_info.size();
314
auto const bounding_rect = displays.bounding_rectangle();
316
auto const total_width = bounding_rect.size.width.as_int();
317
auto const total_height = bounding_rect.size.height.as_int();
321
for (auto& info : session_info)
323
auto const x = (total_width*index)/sessions;
325
auto const dx = (total_width*index)/sessions - x;
327
auto const old_tile = info.second.tile;
328
Rectangle const new_tile{{x, 0}, {dx, total_height}};
330
update_surfaces(info.first, old_tile, new_tile);
332
info.second.tile = new_tile;
336
void update_surfaces(ms::Session const* session, Rectangle const& old_tile, Rectangle const& new_tile)
338
auto displacement = new_tile.top_left - old_tile.top_left;
339
auto& info = session_info[session];
341
for (auto const& ps : info.surfaces)
343
if (auto const surface = ps.lock())
345
auto const old_pos = surface->top_left();
346
surface->move_to(old_pos + displacement);
348
fit_to_new_tile(*surface, old_tile, new_tile);
353
static void clip_to_tile(ms::SurfaceCreationParameters& parameters, Rectangle const& tile)
355
auto const displacement = parameters.top_left - tile.top_left;
357
auto width = std::min(tile.size.width.as_int()-displacement.dx.as_int(), parameters.size.width.as_int());
358
auto height = std::min(tile.size.height.as_int()-displacement.dy.as_int(), parameters.size.height.as_int());
360
parameters.size = Size{width, height};
363
static void fit_to_new_tile(ms::Surface& surface, Rectangle const& old_tile, Rectangle const& new_tile)
365
auto const displacement = surface.top_left() - new_tile.top_left;
367
// For now just scale if was filling width/height of tile
368
auto const old_size = surface.size();
369
auto const scaled_width = old_size.width == old_tile.size.width ? new_tile.size.width : old_size.width;
370
auto const scaled_height = old_size.height == old_tile.size.height ? new_tile.size.height : old_size.height;
372
auto width = std::min(new_tile.size.width.as_int()-displacement.dx.as_int(), scaled_width.as_int());
373
auto height = std::min(new_tile.size.height.as_int()-displacement.dy.as_int(), scaled_height.as_int());
375
surface.resize({width, height});
378
static bool drag(std::shared_ptr<ms::Surface> surface, Point to, Point from, Rectangle bounds)
380
if (surface && surface->input_area_contains(from))
382
auto const top_left = surface->top_left();
383
auto const surface_size = surface->size();
384
auto const bottom_right = top_left + as_displacement(surface_size);
386
auto movement = to - from;
388
if (movement.dx < DeltaX{0})
389
movement.dx = std::max(movement.dx, (bounds.top_left - top_left).dx);
391
if (movement.dy < DeltaY{0})
392
movement.dy = std::max(movement.dy, (bounds.top_left - top_left).dy);
394
if (movement.dx > DeltaX{0})
395
movement.dx = std::min(movement.dx, (bounds.bottom_right() - bottom_right).dx);
397
if (movement.dy > DeltaY{0})
398
movement.dy = std::min(movement.dy, (bounds.bottom_right() - bottom_right).dy);
400
auto new_pos = surface->top_left() + movement;
402
surface->move_to(new_pos);
409
static bool resize(std::shared_ptr<ms::Surface> surface, Point cursor, Point old_cursor, Rectangle bounds)
411
if (surface && surface->input_area_contains(old_cursor))
413
auto const top_left = surface->top_left();
415
auto const old_displacement = old_cursor - top_left;
416
auto const new_displacement = cursor - top_left;
418
auto const scale_x = new_displacement.dx.as_float()/std::max(1.0f, old_displacement.dx.as_float());
419
auto const scale_y = new_displacement.dy.as_float()/std::max(1.0f, old_displacement.dy.as_float());
421
if (scale_x <= 0.0f || scale_y <= 0.0f) return false;
423
auto const old_size = surface->size();
424
Size new_size{scale_x*old_size.width, scale_y*old_size.height};
426
auto const size_limits = as_size(bounds.bottom_right() - top_left);
428
if (new_size.width > size_limits.width)
429
new_size.width = size_limits.width;
431
if (new_size.height > size_limits.height)
432
new_size.height = size_limits.height;
434
surface->resize(new_size);
444
SurfaceInfo() = default;
445
std::weak_ptr<ms::Session> session;
446
MirSurfaceState state;
447
Rectangle restore_rect;
450
void set_state(ms::Surface const& surface, int value)
452
auto new_state = mir_surface_state_restored;
456
case mir_surface_state_restored:
457
new_state = mir_surface_state_restored;
460
case mir_surface_state_maximized:
461
new_state = mir_surface_state_maximized;
464
case mir_surface_state_vertmaximized:
465
new_state = mir_surface_state_vertmaximized;
468
case mir_surface_state_horizmaximized:
469
new_state = mir_surface_state_horizmaximized;
476
for (auto& i : surface_info)
478
if (auto const sp = i.first.lock())
480
if (sp.get() == &surface)
482
auto& surface_info = i.second;
484
if (surface_info.state == mir_surface_state_restored)
486
surface_info.restore_rect = {sp->top_left(), sp->size()};
489
if (surface_info.state == new_state)
491
return; // Nothing to do
494
auto const& session_info =
495
this->session_info[surface_info.session.lock().get()];
499
case mir_surface_state_restored:
500
sp->move_to(surface_info.restore_rect.top_left);
501
sp->resize(surface_info.restore_rect.size);
504
case mir_surface_state_maximized:
505
sp->move_to(session_info.tile.top_left);
506
sp->resize(session_info.tile.size);
509
case mir_surface_state_horizmaximized:
510
sp->move_to({session_info.tile.top_left.x, surface_info.restore_rect.top_left.y});
511
sp->resize({session_info.tile.size.width, surface_info.restore_rect.size.height});
514
case mir_surface_state_vertmaximized:
515
sp->move_to({surface_info.restore_rect.top_left.x, session_info.tile.top_left.y});
516
sp->resize({surface_info.restore_rect.size.width, session_info.tile.size.height});
523
surface_info.state = new_state;
529
std::shared_ptr<ms::Session> session_under(Point position)
531
for(auto& info : session_info)
533
if (info.second.tile.contains(position))
535
return info.second.session.lock();
539
return std::shared_ptr<ms::Session>{};
544
SessionInfo() = default;
545
SessionInfo& operator=(std::weak_ptr<ms::Session> const& session)
547
this->session = session;
551
std::weak_ptr<ms::Session> session;
553
std::vector<std::weak_ptr<ms::Surface>> surfaces;
556
FocusControllerFactory const focus_controller;
561
std::map<ms::Session const*, SessionInfo> session_info;
562
std::map<std::weak_ptr<ms::Surface>, SurfaceInfo, std::owner_less<std::weak_ptr<ms::Surface>>> surface_info;
565
std::weak_ptr<ms::Surface> old_surface;
65
class FullscreenWindowManagerPolicy
68
using Tools = me::BasicWindowManagerToolsCopy<NullSessionInfo, NullSurfaceInfo>;
69
using SessionInfoMap = typename me::SessionTo<NullSessionInfo>::type;
70
using SurfaceInfoMap = typename me::SurfaceTo<NullSurfaceInfo>::type;
72
FullscreenWindowManagerPolicy(Tools* const /*tools*/, std::shared_ptr<msh::DisplayLayout> const& display_layout) :
73
display_layout{display_layout} {}
75
void handle_session_info_updated(SessionInfoMap& /*session_info*/, Rectangles const& /*displays*/) {}
77
void handle_displays_updated(SessionInfoMap& /*session_info*/, Rectangles const& /*displays*/) {}
79
auto handle_place_new_surface(
80
std::shared_ptr<ms::Session> const& /*session*/,
81
ms::SurfaceCreationParameters const& request_parameters)
82
-> ms::SurfaceCreationParameters
84
auto placed_parameters = request_parameters;
86
Rectangle rect{request_parameters.top_left, request_parameters.size};
87
display_layout->size_to_output(rect);
88
placed_parameters.size = rect.size;
90
return placed_parameters;
92
void handle_modify_surface(
93
std::shared_ptr<ms::Session> const& /*session*/,
94
std::shared_ptr<ms::Surface> const& /*surface*/,
95
msh::SurfaceSpecification const& /*modifications*/)
99
void handle_new_surface(std::shared_ptr<ms::Session> const& /*session*/, std::shared_ptr<ms::Surface> const& /*surface*/)
103
void handle_delete_surface(std::shared_ptr<ms::Session> const& /*session*/, std::weak_ptr<ms::Surface> const& /*surface*/) {}
105
int handle_set_state(std::shared_ptr<ms::Surface> const& /*surface*/, MirSurfaceState value)
108
bool handle_keyboard_event(MirKeyboardEvent const* /*event*/) { return false; }
110
bool handle_touch_event(MirTouchEvent const* /*event*/) { return false; }
112
bool handle_pointer_event(MirPointerEvent const* /*event*/) { return false; }
114
void generate_decorations_for(
115
std::shared_ptr<ms::Session> const&,
116
std::shared_ptr<ms::Surface> const&,
121
std::shared_ptr<msh::DisplayLayout> const display_layout;
569
class me::EventTracker : public mi::EventFilter
572
explicit EventTracker(std::shared_ptr<me::WindowManager> const& window_manager) :
573
window_manager{window_manager} {}
575
bool handle(MirEvent const& event) override
579
case mir_event_type_key:
580
return handle_key_event(event.key);
582
case mir_event_type_motion:
583
return handle_motion_event(event.motion);
591
bool handle_key_event(MirKeyEvent const& event)
593
static const int modifier_mask =
594
mir_key_modifier_alt |
595
mir_key_modifier_shift |
596
mir_key_modifier_sym |
597
mir_key_modifier_ctrl |
598
mir_key_modifier_meta;
600
if (event.action == mir_key_action_down &&
601
event.scan_code == KEY_F11)
603
if (auto const wm = window_manager.lock())
604
switch (event.modifiers & modifier_mask)
606
case mir_key_modifier_alt:
607
wm->toggle(mir_surface_state_maximized);
610
case mir_key_modifier_shift:
611
wm->toggle(mir_surface_state_vertmaximized);
614
case mir_key_modifier_ctrl:
615
wm->toggle(mir_surface_state_horizmaximized);
626
bool handle_motion_event(MirMotionEvent const& event)
628
if (event.action == mir_motion_action_down ||
629
event.action == mir_motion_action_pointer_down)
631
if (auto const wm = window_manager.lock())
633
wm->click(average_pointer(event.pointer_count, event.pointer_coordinates));
637
else if (event.action == mir_motion_action_move &&
638
event.modifiers & mir_key_modifier_alt)
640
if (auto const wm = window_manager.lock())
642
switch (event.button_state)
644
case mir_motion_button_primary:
645
wm->drag(average_pointer(event.pointer_count, event.pointer_coordinates));
648
case mir_motion_button_tertiary:
649
wm->resize(average_pointer(event.pointer_count, event.pointer_coordinates));
661
static Point average_pointer(size_t pointer_count, MirMotionPointer const* pointer_coordinates)
666
for (auto p = pointer_coordinates; p != pointer_coordinates + pointer_count; ++p)
672
return Point{total_x/pointer_count, total_y/pointer_count};
675
std::weak_ptr<me::WindowManager> const window_manager;
678
auto me::WindowManagmentFactory::window_manager() -> std::shared_ptr<me::WindowManager>
680
auto tmp = wm.lock();
684
auto const options = server.get_options();
685
auto const selection = options->get<std::string>(wm_option);
687
if (selection == wm_tiling)
689
auto focus_controller_factory = [this] { return server.the_focus_controller(); };
690
tmp = std::make_shared<TilingWindowManager>(focus_controller_factory);
692
else if (selection == wm_fullscreen)
693
tmp = std::make_shared<FullscreenWindowManager>(server.the_shell_display_layout());
126
using TilingWindowManager = me::BasicWindowManagerCopy<me::TilingWindowManagerPolicy, me::TilingSessionInfo, me::TilingSurfaceInfo>;
127
using FullscreenWindowManager = me::BasicWindowManagerCopy<FullscreenWindowManagerPolicy, NullSessionInfo, NullSurfaceInfo>;
128
using CanonicalWindowManager = me::BasicWindowManagerCopy<me::CanonicalWindowManagerPolicyCopy, me::CanonicalSessionInfoCopy, me::CanonicalSurfaceInfoCopy>;
130
void me::add_window_manager_option_to(Server& server)
132
server.add_configuration_option(wm_option, wm_description, wm_canonical);
134
server.override_the_window_manager_builder([&server](msh::FocusController* focus_controller)
135
-> std::shared_ptr<msh::WindowManager>
137
auto const options = server.get_options();
138
auto const selection = options->get<std::string>(wm_option);
140
if (selection == wm_tiling)
142
return std::make_shared<TilingWindowManager>(focus_controller);
144
else if (selection == wm_fullscreen)
146
return std::make_shared<FullscreenWindowManager>(focus_controller, server.the_shell_display_layout());
148
else if (selection == wm_canonical)
150
return std::make_shared<CanonicalWindowManager>(focus_controller, server.the_shell_display_layout());
695
153
throw mir::AbnormalExit("Unknown window manager: " + selection);
697
et = std::make_shared<EventTracker>(tmp);
698
server.the_composite_event_filter()->prepend(et);