2
* Copyright © 2015 Canonical Ltd.
4
* This program is free software: you can redistribute it and/or modify it
5
* under the terms of the GNU General Public License version 3,
6
* as published by the Free Software Foundation.
8
* This program is distributed in the hope that it will be useful,
9
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
* GNU General Public License for more details.
13
* You should have received a copy of the GNU General Public License
14
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16
* Authored By: Alan Griffiths <alan@octopull.co.uk>
19
#include "canonical_window_manager.h"
21
#include "mir/scene/session.h"
22
#include "mir/scene/surface.h"
23
#include "mir/scene/surface_creation_parameters.h"
24
#include "mir/shell/surface_ready_observer.h"
25
#include "mir/shell/display_layout.h"
27
#include <linux/input.h>
31
namespace me = mir::examples;
32
namespace ms = mir::scene;
33
using namespace mir::geometry;
35
///\example canonical_window_manager.cpp
36
// Based on "Mir and Unity: Surfaces, input, and displays (v0.3)"
40
int const title_bar_height = 10;
41
Size titlebar_size_for_window(Size window_size)
43
return {window_size.width, Height{title_bar_height}};
46
Point titlebar_position_for_window(Point window_position)
50
window_position.y - DeltaY(title_bar_height)
55
me::CanonicalWindowManagerPolicyCopy::CanonicalWindowManagerPolicyCopy(
56
WindowManagerTools* const tools,
57
std::shared_ptr<shell::DisplayLayout> const& display_layout) :
59
display_layout{display_layout}
63
void me::CanonicalWindowManagerPolicyCopy::click(Point cursor)
65
if (auto const surface = tools->surface_at(cursor))
66
select_active_surface(surface);
69
void me::CanonicalWindowManagerPolicyCopy::handle_session_info_updated(SessionInfoMap& /*session_info*/, Rectangles const& /*displays*/)
73
void me::CanonicalWindowManagerPolicyCopy::handle_displays_updated(SessionInfoMap& /*session_info*/, Rectangles const& displays)
75
display_area = displays.bounding_rectangle();
77
for (auto const weak_surface : fullscreen_surfaces)
79
if (auto const surface = weak_surface.lock())
81
auto const& info = tools->info_for(weak_surface);
82
Rectangle rect{surface->top_left(), surface->size()};
84
display_layout->place_in_output(info.output_id.value(), rect);
85
surface->move_to(rect.top_left);
86
surface->resize(rect.size);
91
void me::CanonicalWindowManagerPolicyCopy::resize(Point cursor)
93
select_active_surface(tools->surface_at(old_cursor));
94
resize(active_surface(), cursor, old_cursor, display_area);
97
auto me::CanonicalWindowManagerPolicyCopy::handle_place_new_surface(
98
std::shared_ptr<ms::Session> const& session,
99
ms::SurfaceCreationParameters const& request_parameters)
100
-> ms::SurfaceCreationParameters
102
auto parameters = request_parameters;
103
auto surf_type = parameters.type.is_set() ? parameters.type.value() : mir_surface_type_normal;
104
bool const needs_titlebar = SurfaceInfo::needs_titlebar(surf_type);
107
parameters.size.height = parameters.size.height + DeltaY{title_bar_height};
109
if (!parameters.state.is_set())
110
parameters.state = mir_surface_state_restored;
112
auto const active_display = tools->active_display();
114
auto const width = parameters.size.width.as_int();
115
auto const height = parameters.size.height.as_int();
117
bool positioned = false;
119
auto const parent = parameters.parent.lock();
121
if (parameters.output_id != mir::graphics::DisplayConfigurationOutputId{0})
123
Rectangle rect{parameters.top_left, parameters.size};
124
display_layout->place_in_output(parameters.output_id, rect);
125
parameters.top_left = rect.top_left;
126
parameters.size = rect.size;
127
parameters.state = mir_surface_state_fullscreen;
130
else if (!parent) // No parent => client can't suggest positioning
132
if (auto const default_surface = session->default_surface())
134
static Displacement const offset{title_bar_height, title_bar_height};
136
parameters.top_left = default_surface->top_left() + offset;
138
geometry::Rectangle display_for_app{default_surface->top_left(), default_surface->size()};
140
display_layout->size_to_output(display_for_app);
142
positioned = display_for_app.overlaps(Rectangle{parameters.top_left, parameters.size});
146
if (parent && parameters.aux_rect.is_set() && parameters.edge_attachment.is_set())
148
auto const edge_attachment = parameters.edge_attachment.value();
149
auto const aux_rect = parameters.aux_rect.value();
150
auto const parent_top_left = parent->top_left();
151
auto const top_left = aux_rect.top_left -Point{} + parent_top_left;
152
auto const top_right= aux_rect.top_right() -Point{} + parent_top_left;
153
auto const bot_left = aux_rect.bottom_left()-Point{} + parent_top_left;
155
if (edge_attachment & mir_edge_attachment_vertical)
157
if (active_display.contains(top_right + Displacement{width, height}))
159
parameters.top_left = top_right;
162
else if (active_display.contains(top_left + Displacement{-width, height}))
164
parameters.top_left = top_left + Displacement{-width, 0};
169
if (edge_attachment & mir_edge_attachment_horizontal)
171
if (active_display.contains(bot_left + Displacement{width, height}))
173
parameters.top_left = bot_left;
176
else if (active_display.contains(top_left + Displacement{width, -height}))
178
parameters.top_left = top_left + Displacement{0, -height};
185
// o Otherwise, if the dialog is not the same as any previous dialog for the
186
// same parent window, and/or it does not have user-customized position:
187
// o It should be optically centered relative to its parent, unless this
188
// would overlap or cover the title bar of the parent.
189
// o Otherwise, it should be cascaded vertically (but not horizontally)
190
// relative to its parent, unless, this would cause at least part of
191
// it to extend into shell space.
192
auto const parent_top_left = parent->top_left();
193
auto const centred = parent_top_left
194
+ 0.5*(as_displacement(parent->size()) - as_displacement(parameters.size))
195
- DeltaY{(parent->size().height.as_int()-height)/6};
197
parameters.top_left = centred;
203
auto const centred = active_display.top_left
204
+ 0.5*(as_displacement(active_display.size) - as_displacement(parameters.size))
205
- DeltaY{(active_display.size.height.as_int()-height)/6};
207
switch (parameters.state.value())
209
case mir_surface_state_fullscreen:
210
case mir_surface_state_maximized:
211
parameters.top_left = active_display.top_left;
212
parameters.size = active_display.size;
215
case mir_surface_state_vertmaximized:
216
parameters.top_left = centred;
217
parameters.top_left.y = active_display.top_left.y;
218
parameters.size.height = active_display.size.height;
221
case mir_surface_state_horizmaximized:
222
parameters.top_left = centred;
223
parameters.top_left.x = active_display.top_left.x;
224
parameters.size.width = active_display.size.width;
228
parameters.top_left = centred;
231
if (parameters.top_left.y < display_area.top_left.y)
232
parameters.top_left.y = display_area.top_left.y;
235
if (parameters.state != mir_surface_state_fullscreen && needs_titlebar)
237
parameters.top_left.y = parameters.top_left.y + DeltaY{title_bar_height};
238
parameters.size.height = parameters.size.height - DeltaY{title_bar_height};
244
void me::CanonicalWindowManagerPolicyCopy::generate_decorations_for(
245
std::shared_ptr<scene::Session> const& session,
246
std::shared_ptr<scene::Surface> const& surface,
247
SurfaceInfoMap& surface_map,
248
std::function<frontend::SurfaceId(std::shared_ptr<scene::Session> const& session, scene::SurfaceCreationParameters const& params)> const& build)
250
if (!SurfaceInfo::needs_titlebar(surface->type()))
253
auto format = mir_pixel_format_xrgb_8888;
254
ms::SurfaceCreationParameters params;
255
params.of_size(titlebar_size_for_window(surface->size()))
256
.of_name("decoration")
257
.of_pixel_format(format)
258
.of_buffer_usage(mir::graphics::BufferUsage::software)
259
.of_position(titlebar_position_for_window(surface->top_left()))
260
.of_type(mir_surface_type_gloss);
261
auto id = build(session, params);
262
auto titlebar = session->surface(id);
263
titlebar->set_alpha(0.9);
265
auto& surface_info = tools->info_for(surface);
266
surface_info.titlebar = titlebar;
267
surface_info.titlebar_id = id;
268
surface_info.children.push_back(titlebar);
270
SurfaceInfo& titlebar_info =
271
surface_map.emplace(titlebar, SurfaceInfo{session, titlebar, {}}).first->second;
272
titlebar_info.is_titlebar = true;
273
titlebar_info.parent = surface;
274
titlebar_info.init_titlebar(titlebar);
277
void me::CanonicalWindowManagerPolicyCopy::handle_new_surface(std::shared_ptr<ms::Session> const& session, std::shared_ptr<ms::Surface> const& surface)
279
auto& surface_info = tools->info_for(surface);
280
if (auto const parent = surface_info.parent.lock())
282
tools->info_for(parent).children.push_back(surface);
285
tools->info_for(session).surfaces.push_back(surface);
287
if (surface_info.can_be_active())
289
surface->add_observer(std::make_shared<shell::SurfaceReadyObserver>(
290
[this](std::shared_ptr<scene::Session> const& /*session*/,
291
std::shared_ptr<scene::Surface> const& surface)
293
select_active_surface(surface);
299
if (surface_info.state == mir_surface_state_fullscreen)
300
fullscreen_surfaces.insert(surface);
303
void me::CanonicalWindowManagerPolicyCopy::handle_modify_surface(
304
std::shared_ptr<scene::Session> const& session,
305
std::shared_ptr<scene::Surface> const& surface,
306
shell::SurfaceSpecification const& modifications)
308
auto& surface_info_old = tools->info_for(surface);
310
auto surface_info = surface_info_old;
312
if (modifications.parent.is_set())
313
surface_info.parent = modifications.parent.value();
315
if (modifications.type.is_set() &&
316
surface_info.type != modifications.type.value())
318
auto const new_type = modifications.type.value();
320
if (!surface_info.can_morph_to(new_type))
322
throw std::runtime_error("Unsupported surface type change");
325
surface_info.type = new_type;
327
if (surface_info.must_not_have_parent())
329
if (modifications.parent.is_set())
330
throw std::runtime_error("Target surface type does not support parent");
332
surface_info.parent.reset();
334
else if (surface_info.must_have_parent())
336
if (!surface_info.parent.lock())
337
throw std::runtime_error("Target surface type requires parent");
340
surface->configure(mir_surface_attrib_type, new_type);
343
#define COPY_IF_SET(field)\
344
if (modifications.field.is_set())\
345
surface_info.field = modifications.field.value()
347
COPY_IF_SET(min_width);
348
COPY_IF_SET(min_height);
349
COPY_IF_SET(max_width);
350
COPY_IF_SET(max_height);
351
COPY_IF_SET(min_width);
352
COPY_IF_SET(width_inc);
353
COPY_IF_SET(height_inc);
354
COPY_IF_SET(min_aspect);
355
COPY_IF_SET(max_aspect);
356
COPY_IF_SET(output_id);
360
std::swap(surface_info, surface_info_old);
362
if (modifications.name.is_set())
363
surface->rename(modifications.name.value());
365
if (modifications.streams.is_set())
367
auto v = modifications.streams.value();
368
std::vector<shell::StreamSpecification> l (v.begin(), v.end());
369
session->configure_streams(*surface, l);
372
if (modifications.input_shape.is_set())
374
surface->set_input_region(modifications.input_shape.value());
377
if (modifications.width.is_set() || modifications.height.is_set())
379
auto new_size = surface->size();
381
if (modifications.width.is_set())
382
new_size.width = modifications.width.value();
384
if (modifications.height.is_set())
385
new_size.height = modifications.height.value();
387
auto top_left = surface->top_left();
389
surface_info.constrain_resize(
397
apply_resize(surface, surface_info.titlebar, top_left, new_size);
400
if (modifications.state.is_set())
402
auto const state = handle_set_state(surface, modifications.state.value());
403
surface->configure(mir_surface_attrib_state, state);
407
void me::CanonicalWindowManagerPolicyCopy::handle_delete_surface(std::shared_ptr<ms::Session> const& session, std::weak_ptr<ms::Surface> const& surface)
409
fullscreen_surfaces.erase(surface);
411
auto& info = tools->info_for(surface);
413
if (auto const parent = info.parent.lock())
415
auto& siblings = tools->info_for(parent).children;
417
for (auto i = begin(siblings); i != end(siblings); ++i)
419
if (surface.lock() == i->lock())
427
session->destroy_surface(surface);
430
session->destroy_surface(info.titlebar_id);
431
tools->forget(info.titlebar);
434
auto& surfaces = tools->info_for(session).surfaces;
436
for (auto i = begin(surfaces); i != end(surfaces); ++i)
438
if (surface.lock() == i->lock())
445
if (surfaces.empty() && session == tools->focused_session())
447
active_surface_.reset();
448
tools->focus_next_session();
449
select_active_surface(tools->focused_surface());
453
int me::CanonicalWindowManagerPolicyCopy::handle_set_state(std::shared_ptr<ms::Surface> const& surface, MirSurfaceState value)
455
auto& info = tools->info_for(surface);
459
case mir_surface_state_restored:
460
case mir_surface_state_maximized:
461
case mir_surface_state_vertmaximized:
462
case mir_surface_state_horizmaximized:
463
case mir_surface_state_fullscreen:
464
case mir_surface_state_hidden:
465
case mir_surface_state_minimized:
472
if (info.state == mir_surface_state_restored)
474
info.restore_rect = {surface->top_left(), surface->size()};
477
if (info.state != mir_surface_state_fullscreen)
479
info.output_id = decltype(info.output_id){};
480
fullscreen_surfaces.erase(surface);
484
fullscreen_surfaces.insert(surface);
487
if (info.state == value)
492
auto const old_pos = surface->top_left();
493
Displacement movement;
497
case mir_surface_state_restored:
498
movement = info.restore_rect.top_left - old_pos;
499
surface->resize(info.restore_rect.size);
502
info.titlebar->resize(titlebar_size_for_window(info.restore_rect.size));
503
info.titlebar->show();
507
case mir_surface_state_maximized:
508
movement = display_area.top_left - old_pos;
509
surface->resize(display_area.size);
511
info.titlebar->hide();
514
case mir_surface_state_horizmaximized:
515
movement = Point{display_area.top_left.x, info.restore_rect.top_left.y} - old_pos;
516
surface->resize({display_area.size.width, info.restore_rect.size.height});
519
info.titlebar->resize(titlebar_size_for_window({display_area.size.width, info.restore_rect.size.height}));
520
info.titlebar->show();
524
case mir_surface_state_vertmaximized:
525
movement = Point{info.restore_rect.top_left.x, display_area.top_left.y} - old_pos;
526
surface->resize({info.restore_rect.size.width, display_area.size.height});
528
info.titlebar->hide();
531
case mir_surface_state_fullscreen:
533
Rectangle rect{old_pos, surface->size()};
535
if (info.output_id.is_set())
537
display_layout->place_in_output(info.output_id.value(), rect);
541
display_layout->size_to_output(rect);
544
movement = rect.top_left - old_pos;
545
surface->resize(rect.size);
549
case mir_surface_state_hidden:
550
case mir_surface_state_minimized:
552
info.titlebar->hide();
554
return info.state = value;
560
// TODO It is rather simplistic to move a tree WRT the top_left of the root
561
// TODO when resizing. But for more sophistication we would need to encode
562
// TODO some sensible layout rules.
563
move_tree(surface, movement);
567
if (info.is_visible())
573
void me::CanonicalWindowManagerPolicyCopy::drag(Point cursor)
575
select_active_surface(tools->surface_at(old_cursor));
576
drag(active_surface(), cursor, old_cursor, display_area);
579
void me::CanonicalWindowManagerPolicyCopy::handle_raise_surface(
580
std::shared_ptr<ms::Session> const& /*session*/,
581
std::shared_ptr<ms::Surface> const& surface)
583
select_active_surface(surface);
586
bool me::CanonicalWindowManagerPolicyCopy::handle_keyboard_event(MirKeyboardEvent const* event)
588
auto const action = mir_keyboard_event_action(event);
589
auto const scan_code = mir_keyboard_event_scan_code(event);
590
auto const modifiers = mir_keyboard_event_modifiers(event) & modifier_mask;
592
if (action == mir_keyboard_action_down && scan_code == KEY_F11)
596
case mir_input_event_modifier_alt:
597
toggle(mir_surface_state_maximized);
600
case mir_input_event_modifier_shift:
601
toggle(mir_surface_state_vertmaximized);
604
case mir_input_event_modifier_ctrl:
605
toggle(mir_surface_state_horizmaximized);
612
else if (action == mir_keyboard_action_down && scan_code == KEY_F4)
614
if (auto const session = tools->focused_session())
618
case mir_input_event_modifier_alt:
619
kill(session->process_id(), SIGTERM);
622
case mir_input_event_modifier_ctrl:
623
if (auto const surf = session->default_surface())
625
surf->request_client_surface_close();
634
else if (action == mir_keyboard_action_down &&
635
modifiers == mir_input_event_modifier_alt &&
636
scan_code == KEY_TAB)
638
tools->focus_next_session();
639
if (auto const surface = tools->focused_surface())
640
select_active_surface(surface);
644
else if (action == mir_keyboard_action_down &&
645
modifiers == mir_input_event_modifier_alt &&
646
scan_code == KEY_GRAVE)
648
if (auto const prev = tools->focused_surface())
650
if (auto const app = tools->focused_session())
651
select_active_surface(app->surface_after(prev));
660
bool me::CanonicalWindowManagerPolicyCopy::handle_touch_event(MirTouchEvent const* event)
662
auto const count = mir_touch_event_point_count(event);
667
for (auto i = 0U; i != count; ++i)
669
total_x += mir_touch_event_axis_value(event, i, mir_touch_axis_x);
670
total_y += mir_touch_event_axis_value(event, i, mir_touch_axis_y);
673
Point const cursor{total_x/count, total_y/count};
676
for (auto i = 0U; i != count; ++i)
678
switch (mir_touch_event_action(event, i))
680
case mir_touch_action_up:
683
case mir_touch_action_down:
686
case mir_touch_action_change:
691
bool consumes_event = false;
698
consumes_event = true;
703
consumes_event = true;
709
return consumes_event;
712
bool me::CanonicalWindowManagerPolicyCopy::handle_pointer_event(MirPointerEvent const* event)
714
auto const action = mir_pointer_event_action(event);
715
auto const modifiers = mir_pointer_event_modifiers(event) & modifier_mask;
717
mir_pointer_event_axis_value(event, mir_pointer_axis_x),
718
mir_pointer_event_axis_value(event, mir_pointer_axis_y)};
720
bool consumes_event = false;
722
if (action == mir_pointer_action_button_down)
726
else if (action == mir_pointer_action_motion &&
727
modifiers == mir_input_event_modifier_alt)
729
if (mir_pointer_event_button_state(event, mir_pointer_button_primary))
732
consumes_event = true;
735
if (mir_pointer_event_button_state(event, mir_pointer_button_tertiary))
738
consumes_event = true;
741
else if (action == mir_pointer_action_motion && !modifiers)
743
if (mir_pointer_event_button_state(event, mir_pointer_button_primary))
745
if (auto const possible_titlebar = tools->surface_at(old_cursor))
747
if (tools->info_for(possible_titlebar).is_titlebar)
750
consumes_event = true;
757
return consumes_event;
760
void me::CanonicalWindowManagerPolicyCopy::toggle(MirSurfaceState state)
762
if (auto const surface = active_surface())
764
auto& info = tools->info_for(surface);
766
if (info.state == state)
767
state = mir_surface_state_restored;
769
auto const value = handle_set_state(surface, MirSurfaceState(state));
770
surface->configure(mir_surface_attrib_state, value);
774
void me::CanonicalWindowManagerPolicyCopy::select_active_surface(std::shared_ptr<ms::Surface> const& surface)
776
if (surface == active_surface_.lock())
781
if (auto const active_surface = active_surface_.lock())
783
if (auto const titlebar = tools->info_for(active_surface).titlebar)
785
tools->info_for(titlebar).paint_titlebar(0x3F);
789
if (active_surface_.lock())
790
tools->set_focus_to({}, {});
792
active_surface_.reset();
796
auto const& info_for = tools->info_for(surface);
798
if (info_for.can_be_active())
800
if (auto const active_surface = active_surface_.lock())
802
if (auto const titlebar = tools->info_for(active_surface).titlebar)
804
tools->info_for(titlebar).paint_titlebar(0x3F);
807
if (auto const titlebar = tools->info_for(surface).titlebar)
809
tools->info_for(titlebar).paint_titlebar(0xFF);
811
tools->set_focus_to(info_for.session.lock(), surface);
812
tools->raise_tree(surface);
813
active_surface_ = surface;
817
// Cannot have input focus - try the parent
818
if (auto const parent = info_for.parent.lock())
819
select_active_surface(parent);
823
auto me::CanonicalWindowManagerPolicyCopy::active_surface() const
824
-> std::shared_ptr<ms::Surface>
826
if (auto const surface = active_surface_.lock())
829
if (auto const session = tools->focused_session())
831
if (auto const surface = session->default_surface())
835
return std::shared_ptr<ms::Surface>{};
838
bool me::CanonicalWindowManagerPolicyCopy::resize(std::shared_ptr<ms::Surface> const& surface, Point cursor, Point old_cursor, Rectangle bounds)
840
if (!surface || !surface->input_area_contains(old_cursor))
843
auto const top_left = surface->top_left();
844
Rectangle const old_pos{top_left, surface->size()};
846
auto anchor = top_left;
848
for (auto const& corner : {
850
old_pos.bottom_left(),
851
old_pos.bottom_right()})
853
if ((old_cursor - anchor).length_squared() <
854
(old_cursor - corner).length_squared())
860
bool const left_resize = anchor.x != top_left.x;
861
bool const top_resize = anchor.y != top_left.y;
862
int const x_sign = left_resize? -1 : 1;
863
int const y_sign = top_resize? -1 : 1;
865
auto const delta = cursor-old_cursor;
867
Size new_size{old_pos.size.width + x_sign*delta.dx, old_pos.size.height + y_sign*delta.dy};
869
Point new_pos = top_left + left_resize*delta.dx + top_resize*delta.dy;
872
auto const& surface_info = tools->info_for(surface);
874
surface_info.constrain_resize(surface, new_pos, new_size, left_resize, top_resize, bounds);
876
apply_resize(surface, surface_info.titlebar, new_pos, new_size);
881
void me::CanonicalWindowManagerPolicyCopy::apply_resize(
882
std::shared_ptr<ms::Surface> const& surface,
883
std::shared_ptr<ms::Surface> const& titlebar,
884
Point const& new_pos,
885
Size const& new_size) const
888
titlebar->resize({new_size.width, Height{title_bar_height}});
890
surface->resize(new_size);
892
move_tree(surface, new_pos-surface->top_left());
895
bool me::CanonicalWindowManagerPolicyCopy::drag(std::shared_ptr<ms::Surface> surface, Point to, Point from, Rectangle /*bounds*/)
900
if (!surface->input_area_contains(from) && !tools->info_for(surface).titlebar)
903
auto movement = to - from;
905
// placeholder - constrain onscreen
907
switch (tools->info_for(surface).state)
909
case mir_surface_state_restored:
912
// "A vertically maximised surface is anchored to the top and bottom of
913
// the available workspace and can have any width."
914
case mir_surface_state_vertmaximized:
915
movement.dy = DeltaY(0);
918
// "A horizontally maximised surface is anchored to the left and right of
919
// the available workspace and can have any height"
920
case mir_surface_state_horizmaximized:
921
movement.dx = DeltaX(0);
924
// "A maximised surface is anchored to the top, bottom, left and right of the
925
// available workspace. For example, if the launcher is always-visible then
926
// the left-edge of the surface is anchored to the right-edge of the launcher."
927
case mir_surface_state_maximized:
928
case mir_surface_state_fullscreen:
933
move_tree(surface, movement);
938
void me::CanonicalWindowManagerPolicyCopy::move_tree(std::shared_ptr<ms::Surface> const& root, Displacement movement) const
940
root->move_to(root->top_left() + movement);
942
for (auto const& child: tools->info_for(root).children)
944
move_tree(child.lock(), movement);