~alan-griffiths/miral/fix-1717061

« back to all changes in this revision

Viewing changes to smirsh/canonical_window_manager.cpp

  • Committer: Alan Griffiths
  • Date: 2016-03-22 12:52:17 UTC
  • Revision ID: alan@octopull.co.uk-20160322125217-i7hlp9oxxurjlzoj
Initial import of example code

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright © 2015 Canonical Ltd.
 
3
 *
 
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.
 
7
 *
 
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.
 
12
 *
 
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/>.
 
15
 *
 
16
 * Authored By: Alan Griffiths <alan@octopull.co.uk>
 
17
 */
 
18
 
 
19
#include "canonical_window_manager.h"
 
20
 
 
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"
 
26
 
 
27
#include <linux/input.h>
 
28
#include <csignal>
 
29
#include <algorithm>
 
30
 
 
31
namespace me = mir::examples;
 
32
namespace ms = mir::scene;
 
33
using namespace mir::geometry;
 
34
 
 
35
///\example canonical_window_manager.cpp
 
36
// Based on "Mir and Unity: Surfaces, input, and displays (v0.3)"
 
37
 
 
38
namespace
 
39
{
 
40
int const title_bar_height = 10;
 
41
Size titlebar_size_for_window(Size window_size)
 
42
{
 
43
    return {window_size.width, Height{title_bar_height}};
 
44
}
 
45
 
 
46
Point titlebar_position_for_window(Point window_position)
 
47
{
 
48
    return {
 
49
        window_position.x,
 
50
        window_position.y - DeltaY(title_bar_height)
 
51
    };
 
52
}
 
53
}
 
54
 
 
55
me::CanonicalWindowManagerPolicyCopy::CanonicalWindowManagerPolicyCopy(
 
56
    WindowManagerTools* const tools,
 
57
    std::shared_ptr<shell::DisplayLayout> const& display_layout) :
 
58
    tools{tools},
 
59
    display_layout{display_layout}
 
60
{
 
61
}
 
62
 
 
63
void me::CanonicalWindowManagerPolicyCopy::click(Point cursor)
 
64
{
 
65
    if (auto const surface = tools->surface_at(cursor))
 
66
        select_active_surface(surface);
 
67
}
 
68
 
 
69
void me::CanonicalWindowManagerPolicyCopy::handle_session_info_updated(SessionInfoMap& /*session_info*/, Rectangles const& /*displays*/)
 
70
{
 
71
}
 
72
 
 
73
void me::CanonicalWindowManagerPolicyCopy::handle_displays_updated(SessionInfoMap& /*session_info*/, Rectangles const& displays)
 
74
{
 
75
    display_area = displays.bounding_rectangle();
 
76
 
 
77
    for (auto const weak_surface : fullscreen_surfaces)
 
78
    {
 
79
        if (auto const surface = weak_surface.lock())
 
80
        {
 
81
            auto const& info = tools->info_for(weak_surface);
 
82
            Rectangle rect{surface->top_left(), surface->size()};
 
83
 
 
84
            display_layout->place_in_output(info.output_id.value(), rect);
 
85
            surface->move_to(rect.top_left);
 
86
            surface->resize(rect.size);
 
87
        }
 
88
    }
 
89
}
 
90
 
 
91
void me::CanonicalWindowManagerPolicyCopy::resize(Point cursor)
 
92
{
 
93
    select_active_surface(tools->surface_at(old_cursor));
 
94
    resize(active_surface(), cursor, old_cursor, display_area);
 
95
}
 
96
 
 
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
 
101
{
 
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);
 
105
 
 
106
    if (needs_titlebar)
 
107
        parameters.size.height = parameters.size.height + DeltaY{title_bar_height};
 
108
 
 
109
    if (!parameters.state.is_set())
 
110
        parameters.state = mir_surface_state_restored;
 
111
 
 
112
    auto const active_display = tools->active_display();
 
113
 
 
114
    auto const width = parameters.size.width.as_int();
 
115
    auto const height = parameters.size.height.as_int();
 
116
 
 
117
    bool positioned = false;
 
118
 
 
119
    auto const parent = parameters.parent.lock();
 
120
 
 
121
    if (parameters.output_id != mir::graphics::DisplayConfigurationOutputId{0})
 
122
    {
 
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;
 
128
        positioned = true;
 
129
    }
 
130
    else if (!parent) // No parent => client can't suggest positioning
 
131
    {
 
132
        if (auto const default_surface = session->default_surface())
 
133
        {
 
134
            static Displacement const offset{title_bar_height, title_bar_height};
 
135
 
 
136
            parameters.top_left = default_surface->top_left() + offset;
 
137
 
 
138
            geometry::Rectangle display_for_app{default_surface->top_left(), default_surface->size()};
 
139
 
 
140
            display_layout->size_to_output(display_for_app);
 
141
 
 
142
            positioned = display_for_app.overlaps(Rectangle{parameters.top_left, parameters.size});
 
143
        }
 
144
    }
 
145
 
 
146
    if (parent && parameters.aux_rect.is_set() && parameters.edge_attachment.is_set())
 
147
    {
 
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;
 
154
 
 
155
        if (edge_attachment & mir_edge_attachment_vertical)
 
156
        {
 
157
            if (active_display.contains(top_right + Displacement{width, height}))
 
158
            {
 
159
                parameters.top_left = top_right;
 
160
                positioned = true;
 
161
            }
 
162
            else if (active_display.contains(top_left + Displacement{-width, height}))
 
163
            {
 
164
                parameters.top_left = top_left + Displacement{-width, 0};
 
165
                positioned = true;
 
166
            }
 
167
        }
 
168
 
 
169
        if (edge_attachment & mir_edge_attachment_horizontal)
 
170
        {
 
171
            if (active_display.contains(bot_left + Displacement{width, height}))
 
172
            {
 
173
                parameters.top_left = bot_left;
 
174
                positioned = true;
 
175
            }
 
176
            else if (active_display.contains(top_left + Displacement{width, -height}))
 
177
            {
 
178
                parameters.top_left = top_left + Displacement{0, -height};
 
179
                positioned = true;
 
180
            }
 
181
        }
 
182
    }
 
183
    else if (parent)
 
184
    {
 
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};
 
196
 
 
197
        parameters.top_left = centred;
 
198
        positioned = true;
 
199
    }
 
200
 
 
201
    if (!positioned)
 
202
    {
 
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};
 
206
 
 
207
        switch (parameters.state.value())
 
208
        {
 
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;
 
213
            break;
 
214
 
 
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;
 
219
            break;
 
220
 
 
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;
 
225
            break;
 
226
 
 
227
        default:
 
228
            parameters.top_left = centred;
 
229
        }
 
230
 
 
231
        if (parameters.top_left.y < display_area.top_left.y)
 
232
            parameters.top_left.y = display_area.top_left.y;
 
233
    }
 
234
 
 
235
    if (parameters.state != mir_surface_state_fullscreen && needs_titlebar)
 
236
    {
 
237
        parameters.top_left.y = parameters.top_left.y + DeltaY{title_bar_height};
 
238
        parameters.size.height = parameters.size.height - DeltaY{title_bar_height};
 
239
    }
 
240
 
 
241
    return parameters;
 
242
}
 
243
 
 
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)
 
249
{
 
250
    if (!SurfaceInfo::needs_titlebar(surface->type()))
 
251
        return;
 
252
 
 
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);
 
264
 
 
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);
 
269
 
 
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);
 
275
}
 
276
 
 
277
void me::CanonicalWindowManagerPolicyCopy::handle_new_surface(std::shared_ptr<ms::Session> const& session, std::shared_ptr<ms::Surface> const& surface)
 
278
{
 
279
    auto& surface_info = tools->info_for(surface);
 
280
    if (auto const parent = surface_info.parent.lock())
 
281
    {
 
282
        tools->info_for(parent).children.push_back(surface);
 
283
    }
 
284
 
 
285
    tools->info_for(session).surfaces.push_back(surface);
 
286
 
 
287
    if (surface_info.can_be_active())
 
288
    {
 
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)
 
292
                {
 
293
                    select_active_surface(surface);
 
294
                },
 
295
            session,
 
296
            surface));
 
297
    }
 
298
 
 
299
    if (surface_info.state == mir_surface_state_fullscreen)
 
300
        fullscreen_surfaces.insert(surface);
 
301
}
 
302
 
 
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)
 
307
{
 
308
    auto& surface_info_old = tools->info_for(surface);
 
309
 
 
310
    auto surface_info = surface_info_old;
 
311
 
 
312
    if (modifications.parent.is_set())
 
313
        surface_info.parent = modifications.parent.value();
 
314
 
 
315
    if (modifications.type.is_set() &&
 
316
        surface_info.type != modifications.type.value())
 
317
    {
 
318
        auto const new_type = modifications.type.value();
 
319
 
 
320
        if (!surface_info.can_morph_to(new_type))
 
321
        {
 
322
            throw std::runtime_error("Unsupported surface type change");
 
323
        }
 
324
 
 
325
        surface_info.type = new_type;
 
326
 
 
327
        if (surface_info.must_not_have_parent())
 
328
        {
 
329
            if (modifications.parent.is_set())
 
330
                throw std::runtime_error("Target surface type does not support parent");
 
331
 
 
332
            surface_info.parent.reset();
 
333
        }
 
334
        else if (surface_info.must_have_parent())
 
335
        {
 
336
            if (!surface_info.parent.lock())
 
337
                throw std::runtime_error("Target surface type requires parent");
 
338
        }
 
339
 
 
340
        surface->configure(mir_surface_attrib_type, new_type);
 
341
    }
 
342
 
 
343
    #define COPY_IF_SET(field)\
 
344
        if (modifications.field.is_set())\
 
345
            surface_info.field = modifications.field.value()
 
346
 
 
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);
 
357
 
 
358
    #undef COPY_IF_SET
 
359
 
 
360
    std::swap(surface_info, surface_info_old);
 
361
 
 
362
    if (modifications.name.is_set())
 
363
        surface->rename(modifications.name.value());
 
364
 
 
365
    if (modifications.streams.is_set())
 
366
    {
 
367
        auto v = modifications.streams.value();
 
368
        std::vector<shell::StreamSpecification> l (v.begin(), v.end());
 
369
        session->configure_streams(*surface, l);
 
370
    }
 
371
 
 
372
    if (modifications.input_shape.is_set())
 
373
    {
 
374
        surface->set_input_region(modifications.input_shape.value());
 
375
    }
 
376
 
 
377
    if (modifications.width.is_set() || modifications.height.is_set())
 
378
    {
 
379
        auto new_size = surface->size();
 
380
 
 
381
        if (modifications.width.is_set())
 
382
            new_size.width = modifications.width.value();
 
383
 
 
384
        if (modifications.height.is_set())
 
385
            new_size.height = modifications.height.value();
 
386
 
 
387
        auto top_left = surface->top_left();
 
388
 
 
389
        surface_info.constrain_resize(
 
390
            surface,
 
391
            top_left,
 
392
            new_size,
 
393
            false,
 
394
            false,
 
395
            display_area);
 
396
 
 
397
        apply_resize(surface, surface_info.titlebar, top_left, new_size);
 
398
    }
 
399
 
 
400
    if (modifications.state.is_set())
 
401
    {
 
402
        auto const state = handle_set_state(surface, modifications.state.value());
 
403
        surface->configure(mir_surface_attrib_state, state);
 
404
    }
 
405
}
 
406
 
 
407
void me::CanonicalWindowManagerPolicyCopy::handle_delete_surface(std::shared_ptr<ms::Session> const& session, std::weak_ptr<ms::Surface> const& surface)
 
408
{
 
409
    fullscreen_surfaces.erase(surface);
 
410
 
 
411
    auto& info = tools->info_for(surface);
 
412
 
 
413
    if (auto const parent = info.parent.lock())
 
414
    {
 
415
        auto& siblings = tools->info_for(parent).children;
 
416
 
 
417
        for (auto i = begin(siblings); i != end(siblings); ++i)
 
418
        {
 
419
            if (surface.lock() == i->lock())
 
420
            {
 
421
                siblings.erase(i);
 
422
                break;
 
423
            }
 
424
        }
 
425
    }
 
426
 
 
427
    session->destroy_surface(surface);
 
428
    if (info.titlebar)
 
429
    {
 
430
        session->destroy_surface(info.titlebar_id);
 
431
        tools->forget(info.titlebar);
 
432
    }
 
433
 
 
434
    auto& surfaces = tools->info_for(session).surfaces;
 
435
 
 
436
    for (auto i = begin(surfaces); i != end(surfaces); ++i)
 
437
    {
 
438
        if (surface.lock() == i->lock())
 
439
        {
 
440
            surfaces.erase(i);
 
441
            break;
 
442
        }
 
443
    }
 
444
 
 
445
    if (surfaces.empty() && session == tools->focused_session())
 
446
    {
 
447
        active_surface_.reset();
 
448
        tools->focus_next_session();
 
449
        select_active_surface(tools->focused_surface());
 
450
    }
 
451
}
 
452
 
 
453
int me::CanonicalWindowManagerPolicyCopy::handle_set_state(std::shared_ptr<ms::Surface> const& surface, MirSurfaceState value)
 
454
{
 
455
    auto& info = tools->info_for(surface);
 
456
 
 
457
    switch (value)
 
458
    {
 
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:
 
466
        break;
 
467
 
 
468
    default:
 
469
        return info.state;
 
470
    }
 
471
 
 
472
    if (info.state == mir_surface_state_restored)
 
473
    {
 
474
        info.restore_rect = {surface->top_left(), surface->size()};
 
475
    }
 
476
 
 
477
    if (info.state != mir_surface_state_fullscreen)
 
478
    {
 
479
        info.output_id = decltype(info.output_id){};
 
480
        fullscreen_surfaces.erase(surface);
 
481
    }
 
482
    else
 
483
    {
 
484
        fullscreen_surfaces.insert(surface);
 
485
    }
 
486
 
 
487
    if (info.state == value)
 
488
    {
 
489
        return info.state;
 
490
    }
 
491
 
 
492
    auto const old_pos = surface->top_left();
 
493
    Displacement movement;
 
494
 
 
495
    switch (value)
 
496
    {
 
497
    case mir_surface_state_restored:
 
498
        movement = info.restore_rect.top_left - old_pos;
 
499
        surface->resize(info.restore_rect.size);
 
500
        if (info.titlebar)
 
501
        {
 
502
            info.titlebar->resize(titlebar_size_for_window(info.restore_rect.size));
 
503
            info.titlebar->show();
 
504
        }
 
505
        break;
 
506
 
 
507
    case mir_surface_state_maximized:
 
508
        movement = display_area.top_left - old_pos;
 
509
        surface->resize(display_area.size);
 
510
        if (info.titlebar)
 
511
            info.titlebar->hide();
 
512
        break;
 
513
 
 
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});
 
517
        if (info.titlebar)
 
518
        {
 
519
            info.titlebar->resize(titlebar_size_for_window({display_area.size.width, info.restore_rect.size.height}));
 
520
            info.titlebar->show();
 
521
        }
 
522
        break;
 
523
 
 
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});
 
527
        if (info.titlebar)
 
528
            info.titlebar->hide();
 
529
        break;
 
530
 
 
531
    case mir_surface_state_fullscreen:
 
532
    {
 
533
        Rectangle rect{old_pos, surface->size()};
 
534
 
 
535
        if (info.output_id.is_set())
 
536
        {
 
537
            display_layout->place_in_output(info.output_id.value(), rect);
 
538
        }
 
539
        else
 
540
        {
 
541
            display_layout->size_to_output(rect);
 
542
        }
 
543
 
 
544
        movement = rect.top_left - old_pos;
 
545
        surface->resize(rect.size);
 
546
        break;
 
547
    }
 
548
 
 
549
    case mir_surface_state_hidden:
 
550
    case mir_surface_state_minimized:
 
551
        if (info.titlebar)
 
552
            info.titlebar->hide();
 
553
        surface->hide();
 
554
        return info.state = value;
 
555
 
 
556
    default:
 
557
        break;
 
558
    }
 
559
 
 
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);
 
564
 
 
565
    info.state = value;
 
566
 
 
567
    if (info.is_visible())
 
568
        surface->show();
 
569
 
 
570
    return info.state;
 
571
}
 
572
 
 
573
void me::CanonicalWindowManagerPolicyCopy::drag(Point cursor)
 
574
{
 
575
    select_active_surface(tools->surface_at(old_cursor));
 
576
    drag(active_surface(), cursor, old_cursor, display_area);
 
577
}
 
578
 
 
579
void me::CanonicalWindowManagerPolicyCopy::handle_raise_surface(
 
580
    std::shared_ptr<ms::Session> const& /*session*/,
 
581
    std::shared_ptr<ms::Surface> const& surface)
 
582
{
 
583
    select_active_surface(surface);
 
584
}
 
585
 
 
586
bool me::CanonicalWindowManagerPolicyCopy::handle_keyboard_event(MirKeyboardEvent const* event)
 
587
{
 
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;
 
591
 
 
592
    if (action == mir_keyboard_action_down && scan_code == KEY_F11)
 
593
    {
 
594
        switch (modifiers)
 
595
        {
 
596
        case mir_input_event_modifier_alt:
 
597
            toggle(mir_surface_state_maximized);
 
598
            return true;
 
599
 
 
600
        case mir_input_event_modifier_shift:
 
601
            toggle(mir_surface_state_vertmaximized);
 
602
            return true;
 
603
 
 
604
        case mir_input_event_modifier_ctrl:
 
605
            toggle(mir_surface_state_horizmaximized);
 
606
            return true;
 
607
 
 
608
        default:
 
609
            break;
 
610
        }
 
611
    }
 
612
    else if (action == mir_keyboard_action_down && scan_code == KEY_F4)
 
613
    {
 
614
        if (auto const session = tools->focused_session())
 
615
        {
 
616
            switch (modifiers)
 
617
            {
 
618
            case mir_input_event_modifier_alt:
 
619
                kill(session->process_id(), SIGTERM);
 
620
                return true;
 
621
 
 
622
            case mir_input_event_modifier_ctrl:
 
623
                if (auto const surf = session->default_surface())
 
624
                {
 
625
                    surf->request_client_surface_close();
 
626
                    return true;
 
627
                }
 
628
 
 
629
            default:
 
630
                break;
 
631
            }
 
632
        }
 
633
    }
 
634
    else if (action == mir_keyboard_action_down &&
 
635
            modifiers == mir_input_event_modifier_alt &&
 
636
            scan_code == KEY_TAB)
 
637
    {
 
638
        tools->focus_next_session();
 
639
        if (auto const surface = tools->focused_surface())
 
640
            select_active_surface(surface);
 
641
 
 
642
        return true;
 
643
    }
 
644
    else if (action == mir_keyboard_action_down &&
 
645
            modifiers == mir_input_event_modifier_alt &&
 
646
            scan_code == KEY_GRAVE)
 
647
    {
 
648
        if (auto const prev = tools->focused_surface())
 
649
        {
 
650
            if (auto const app = tools->focused_session())
 
651
                select_active_surface(app->surface_after(prev));
 
652
        }
 
653
 
 
654
        return true;
 
655
    }
 
656
 
 
657
    return false;
 
658
}
 
659
 
 
660
bool me::CanonicalWindowManagerPolicyCopy::handle_touch_event(MirTouchEvent const* event)
 
661
{
 
662
    auto const count = mir_touch_event_point_count(event);
 
663
 
 
664
    long total_x = 0;
 
665
    long total_y = 0;
 
666
 
 
667
    for (auto i = 0U; i != count; ++i)
 
668
    {
 
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);
 
671
    }
 
672
 
 
673
    Point const cursor{total_x/count, total_y/count};
 
674
 
 
675
    bool is_drag = true;
 
676
    for (auto i = 0U; i != count; ++i)
 
677
    {
 
678
        switch (mir_touch_event_action(event, i))
 
679
        {
 
680
        case mir_touch_action_up:
 
681
            return false;
 
682
 
 
683
        case mir_touch_action_down:
 
684
            is_drag = false;
 
685
 
 
686
        case mir_touch_action_change:
 
687
            continue;
 
688
        }
 
689
    }
 
690
 
 
691
    bool consumes_event = false;
 
692
    if (is_drag)
 
693
    {
 
694
        switch (count)
 
695
        {
 
696
        case 2:
 
697
            resize(cursor);
 
698
            consumes_event = true;
 
699
            break;
 
700
 
 
701
        case 3:
 
702
            drag(cursor);
 
703
            consumes_event = true;
 
704
            break;
 
705
        }
 
706
    }
 
707
 
 
708
    old_cursor = cursor;
 
709
    return consumes_event;
 
710
}
 
711
 
 
712
bool me::CanonicalWindowManagerPolicyCopy::handle_pointer_event(MirPointerEvent const* event)
 
713
{
 
714
    auto const action = mir_pointer_event_action(event);
 
715
    auto const modifiers = mir_pointer_event_modifiers(event) & modifier_mask;
 
716
    Point const cursor{
 
717
        mir_pointer_event_axis_value(event, mir_pointer_axis_x),
 
718
        mir_pointer_event_axis_value(event, mir_pointer_axis_y)};
 
719
 
 
720
    bool consumes_event = false;
 
721
 
 
722
    if (action == mir_pointer_action_button_down)
 
723
    {
 
724
        click(cursor);
 
725
    }
 
726
    else if (action == mir_pointer_action_motion &&
 
727
             modifiers == mir_input_event_modifier_alt)
 
728
    {
 
729
        if (mir_pointer_event_button_state(event, mir_pointer_button_primary))
 
730
        {
 
731
            drag(cursor);
 
732
            consumes_event = true;
 
733
        }
 
734
 
 
735
        if (mir_pointer_event_button_state(event, mir_pointer_button_tertiary))
 
736
        {
 
737
            resize(cursor);
 
738
            consumes_event = true;
 
739
        }
 
740
    }
 
741
    else if (action == mir_pointer_action_motion && !modifiers)
 
742
    {
 
743
        if (mir_pointer_event_button_state(event, mir_pointer_button_primary))
 
744
        {
 
745
            if (auto const possible_titlebar = tools->surface_at(old_cursor))
 
746
            {
 
747
                if (tools->info_for(possible_titlebar).is_titlebar)
 
748
                {
 
749
                    drag(cursor);
 
750
                    consumes_event = true;
 
751
                }
 
752
            }
 
753
        }
 
754
    }
 
755
 
 
756
    old_cursor = cursor;
 
757
    return consumes_event;
 
758
}
 
759
 
 
760
void me::CanonicalWindowManagerPolicyCopy::toggle(MirSurfaceState state)
 
761
{
 
762
    if (auto const surface = active_surface())
 
763
    {
 
764
        auto& info = tools->info_for(surface);
 
765
 
 
766
        if (info.state == state)
 
767
            state = mir_surface_state_restored;
 
768
 
 
769
        auto const value = handle_set_state(surface, MirSurfaceState(state));
 
770
        surface->configure(mir_surface_attrib_state, value);
 
771
    }
 
772
}
 
773
 
 
774
void me::CanonicalWindowManagerPolicyCopy::select_active_surface(std::shared_ptr<ms::Surface> const& surface)
 
775
{
 
776
    if (surface == active_surface_.lock())
 
777
        return;
 
778
 
 
779
    if (!surface)
 
780
    {
 
781
        if (auto const active_surface = active_surface_.lock())
 
782
        {
 
783
            if (auto const titlebar = tools->info_for(active_surface).titlebar)
 
784
            {
 
785
                tools->info_for(titlebar).paint_titlebar(0x3F);
 
786
            }
 
787
        }
 
788
 
 
789
        if (active_surface_.lock())
 
790
            tools->set_focus_to({}, {});
 
791
 
 
792
        active_surface_.reset();
 
793
        return;
 
794
    }
 
795
 
 
796
    auto const& info_for = tools->info_for(surface);
 
797
 
 
798
    if (info_for.can_be_active())
 
799
    {
 
800
        if (auto const active_surface = active_surface_.lock())
 
801
        {
 
802
            if (auto const titlebar = tools->info_for(active_surface).titlebar)
 
803
            {
 
804
                tools->info_for(titlebar).paint_titlebar(0x3F);
 
805
            }
 
806
        }
 
807
        if (auto const titlebar = tools->info_for(surface).titlebar)
 
808
        {
 
809
            tools->info_for(titlebar).paint_titlebar(0xFF);
 
810
        }
 
811
        tools->set_focus_to(info_for.session.lock(), surface);
 
812
        tools->raise_tree(surface);
 
813
        active_surface_ = surface;
 
814
    }
 
815
    else
 
816
    {
 
817
        // Cannot have input focus - try the parent
 
818
        if (auto const parent = info_for.parent.lock())
 
819
            select_active_surface(parent);
 
820
    }
 
821
}
 
822
 
 
823
auto me::CanonicalWindowManagerPolicyCopy::active_surface() const
 
824
-> std::shared_ptr<ms::Surface>
 
825
{
 
826
    if (auto const surface = active_surface_.lock())
 
827
        return surface;
 
828
 
 
829
    if (auto const session = tools->focused_session())
 
830
    {
 
831
        if (auto const surface = session->default_surface())
 
832
            return surface;
 
833
    }
 
834
 
 
835
    return std::shared_ptr<ms::Surface>{};
 
836
}
 
837
 
 
838
bool me::CanonicalWindowManagerPolicyCopy::resize(std::shared_ptr<ms::Surface> const& surface, Point cursor, Point old_cursor, Rectangle bounds)
 
839
{
 
840
    if (!surface || !surface->input_area_contains(old_cursor))
 
841
        return false;
 
842
 
 
843
    auto const top_left = surface->top_left();
 
844
    Rectangle const old_pos{top_left, surface->size()};
 
845
 
 
846
    auto anchor = top_left;
 
847
 
 
848
    for (auto const& corner : {
 
849
        old_pos.top_right(),
 
850
        old_pos.bottom_left(),
 
851
        old_pos.bottom_right()})
 
852
    {
 
853
        if ((old_cursor - anchor).length_squared() <
 
854
            (old_cursor - corner).length_squared())
 
855
        {
 
856
            anchor = corner;
 
857
        }
 
858
    }
 
859
 
 
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;
 
864
 
 
865
    auto const delta = cursor-old_cursor;
 
866
 
 
867
    Size new_size{old_pos.size.width + x_sign*delta.dx, old_pos.size.height + y_sign*delta.dy};
 
868
 
 
869
    Point new_pos = top_left + left_resize*delta.dx + top_resize*delta.dy;
 
870
 
 
871
 
 
872
    auto const& surface_info = tools->info_for(surface);
 
873
 
 
874
    surface_info.constrain_resize(surface, new_pos, new_size, left_resize, top_resize, bounds);
 
875
 
 
876
    apply_resize(surface, surface_info.titlebar, new_pos, new_size);
 
877
 
 
878
    return true;
 
879
}
 
880
 
 
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
 
886
{
 
887
    if (titlebar)
 
888
        titlebar->resize({new_size.width, Height{title_bar_height}});
 
889
 
 
890
    surface->resize(new_size);
 
891
 
 
892
    move_tree(surface, new_pos-surface->top_left());
 
893
}
 
894
 
 
895
bool me::CanonicalWindowManagerPolicyCopy::drag(std::shared_ptr<ms::Surface> surface, Point to, Point from, Rectangle /*bounds*/)
 
896
{
 
897
    if (!surface)
 
898
        return false;
 
899
 
 
900
    if (!surface->input_area_contains(from) && !tools->info_for(surface).titlebar)
 
901
        return false;
 
902
 
 
903
    auto movement = to - from;
 
904
 
 
905
    // placeholder - constrain onscreen
 
906
 
 
907
    switch (tools->info_for(surface).state)
 
908
    {
 
909
    case mir_surface_state_restored:
 
910
        break;
 
911
 
 
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);
 
916
        break;
 
917
 
 
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);
 
922
        break;
 
923
 
 
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:
 
929
    default:
 
930
        return true;
 
931
    }
 
932
 
 
933
    move_tree(surface, movement);
 
934
 
 
935
    return true;
 
936
}
 
937
 
 
938
void me::CanonicalWindowManagerPolicyCopy::move_tree(std::shared_ptr<ms::Surface> const& root, Displacement movement) const
 
939
{
 
940
    root->move_to(root->top_left() + movement);
 
941
 
 
942
    for (auto const& child: tools->info_for(root).children)
 
943
    {
 
944
        move_tree(child.lock(), movement);
 
945
    }
 
946
}