blob: fe7a3a7174dec59a420c6fd6d16be718b272f7bc [file] [log] [blame]
Kristian Høgsberg1ef23132013-12-04 00:20:01 -08001/*
2 * Copyright © 2010-2012 Intel Corporation
3 * Copyright © 2011-2012 Collabora, Ltd.
4 * Copyright © 2013 Raspberry Pi Foundation
5 *
6 * Permission to use, copy, modify, distribute, and sell this software and
7 * its documentation for any purpose is hereby granted without fee, provided
8 * that the above copyright notice appear in all copies and that both that
9 * copyright notice and this permission notice appear in supporting
10 * documentation, and that the name of the copyright holders not be used in
11 * advertising or publicity pertaining to distribution of the software
12 * without specific, written prior permission. The copyright holders make
13 * no representations about the suitability of this software for any
14 * purpose. It is provided "as is" without express or implied warranty.
15 *
16 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
17 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
18 * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
19 * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
20 * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
21 * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
22 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23 */
24
25#include "config.h"
26
27#include <linux/input.h>
28
29#include "shell.h"
30
31struct exposay_surface {
32 struct desktop_shell *shell;
33 struct weston_surface *surface;
34 struct weston_view *view;
35 struct wl_list link;
36
37 int x;
38 int y;
39 int width;
40 int height;
41 double scale;
42
43 int row;
44 int column;
45
46 /* The animations only apply a transformation for their own lifetime,
47 * and don't have an option to indefinitely maintain the
48 * transformation in a steady state - so, we apply our own once the
49 * animation has finished. */
50 struct weston_transform transform;
51};
52
53static void exposay_set_state(struct desktop_shell *shell,
54 enum exposay_target_state state,
55 struct weston_seat *seat);
56static void exposay_check_state(struct desktop_shell *shell);
57
58static void
59exposay_in_flight_inc(struct desktop_shell *shell)
60{
61 shell->exposay.in_flight++;
62}
63
64static void
65exposay_in_flight_dec(struct desktop_shell *shell)
66{
67 if (--shell->exposay.in_flight > 0)
68 return;
69
70 exposay_check_state(shell);
71}
72
73static void
74exposay_animate_in_done(struct weston_view_animation *animation, void *data)
75{
76 struct exposay_surface *esurface = data;
77
78 wl_list_insert(&esurface->view->geometry.transformation_list,
79 &esurface->transform.link);
80 weston_matrix_init(&esurface->transform.matrix);
81 weston_matrix_scale(&esurface->transform.matrix,
82 esurface->scale, esurface->scale, 1.0f);
83 weston_matrix_translate(&esurface->transform.matrix,
84 esurface->x - esurface->view->geometry.x,
85 esurface->y - esurface->view->geometry.y,
86 0);
87
88 weston_view_geometry_dirty(esurface->view);
89 weston_compositor_schedule_repaint(esurface->view->surface->compositor);
90
91 exposay_in_flight_dec(esurface->shell);
92}
93
94static void
95exposay_animate_in(struct exposay_surface *esurface)
96{
97 exposay_in_flight_inc(esurface->shell);
98
99 weston_move_scale_run(esurface->view,
100 esurface->x - esurface->view->geometry.x,
101 esurface->y - esurface->view->geometry.y,
102 1.0, esurface->scale, 0,
103 exposay_animate_in_done, esurface);
104}
105
106static void
107exposay_animate_out_done(struct weston_view_animation *animation, void *data)
108{
109 struct exposay_surface *esurface = data;
110 struct desktop_shell *shell = esurface->shell;
111
112 wl_list_remove(&esurface->link);
113 free(esurface);
114
115 exposay_in_flight_dec(shell);
116}
117
118static void
119exposay_animate_out(struct exposay_surface *esurface)
120{
121 exposay_in_flight_inc(esurface->shell);
122
123 /* Remove the static transformation set up by
124 * exposay_transform_in_done(). */
125 wl_list_remove(&esurface->transform.link);
126 weston_view_geometry_dirty(esurface->view);
127
128 weston_move_scale_run(esurface->view,
129 esurface->x - esurface->view->geometry.x,
130 esurface->y - esurface->view->geometry.y,
131 1.0, esurface->scale, 1,
132 exposay_animate_out_done, esurface);
133}
134
135static void
136exposay_highlight_surface(struct desktop_shell *shell,
137 struct exposay_surface *esurface)
138{
U. Artie Eoff6d6d1902014-01-15 13:42:18 -0800139 struct weston_view *view = esurface->view;
Kristian Høgsberg1ef23132013-12-04 00:20:01 -0800140
U. Artie Eoff6d6d1902014-01-15 13:42:18 -0800141 shell->exposay.row_current = esurface->row;
142 shell->exposay.column_current = esurface->column;
Kristian Høgsberg1ef23132013-12-04 00:20:01 -0800143
144 activate(shell, view->surface, shell->exposay.seat);
145 shell->exposay.focus_current = view;
146}
147
148static int
149exposay_is_animating(struct desktop_shell *shell)
150{
151 if (shell->exposay.state_cur == EXPOSAY_LAYOUT_INACTIVE ||
152 shell->exposay.state_cur == EXPOSAY_LAYOUT_OVERVIEW)
153 return 0;
154
155 return (shell->exposay.in_flight > 0);
156}
157
158static void
159exposay_pick(struct desktop_shell *shell, int x, int y)
160{
161 struct exposay_surface *esurface;
162
163 if (exposay_is_animating(shell))
164 return;
165
166 wl_list_for_each(esurface, &shell->exposay.surface_list, link) {
167 if (x < esurface->x || x > esurface->x + esurface->width)
168 continue;
169 if (y < esurface->y || y > esurface->y + esurface->height)
170 continue;
171
172 exposay_highlight_surface(shell, esurface);
173 return;
174 }
175}
176
177/* Pretty lame layout for now; just tries to make a square. Should take
178 * aspect ratio into account really. Also needs to be notified of surface
179 * addition and removal and adjust layout/animate accordingly. */
180static enum exposay_layout_state
181exposay_layout(struct desktop_shell *shell)
182{
183 struct workspace *workspace = shell->exposay.workspace;
184 struct weston_compositor *compositor = shell->compositor;
185 struct weston_output *output = get_default_output(compositor);
186 struct weston_view *view;
Emilio Pozuelo Monforte6bbe5a2014-01-07 16:41:39 +0100187 struct exposay_surface *esurface, *highlight = NULL;
Kristian Høgsberg1ef23132013-12-04 00:20:01 -0800188 int w, h;
189 int i;
190 int last_row_removed = 0;
191
192 wl_list_init(&shell->exposay.surface_list);
193
194 shell->exposay.num_surfaces = 0;
195 wl_list_for_each(view, &workspace->layer.view_list, layer_link) {
196 if (!get_shell_surface(view->surface))
197 continue;
198 shell->exposay.num_surfaces++;
199 }
200
201 if (shell->exposay.num_surfaces == 0) {
202 shell->exposay.grid_size = 0;
203 shell->exposay.hpadding_outer = 0;
204 shell->exposay.vpadding_outer = 0;
205 shell->exposay.padding_inner = 0;
206 shell->exposay.surface_size = 0;
207 return EXPOSAY_LAYOUT_OVERVIEW;
208 }
209
210 /* Lay the grid out as square as possible, losing surfaces from the
211 * bottom row if required. Start with fixed padding of a 10% margin
212 * around the outside and 80px internal padding between surfaces, and
213 * maximise the area made available to surfaces after this, but only
214 * to a maximum of 1/3rd the total output size.
215 *
216 * If we can't make a square grid, add one extra row at the bottom
217 * which will have a smaller number of columns.
218 *
219 * XXX: Surely there has to be a better way to express this maths,
220 * right?!
221 */
222 shell->exposay.grid_size = floor(sqrtf(shell->exposay.num_surfaces));
223 if (pow(shell->exposay.grid_size, 2) != shell->exposay.num_surfaces)
224 shell->exposay.grid_size++;
225 last_row_removed = pow(shell->exposay.grid_size, 2) - shell->exposay.num_surfaces;
226
227 shell->exposay.hpadding_outer = (output->width / 10);
228 shell->exposay.vpadding_outer = (output->height / 10);
229 shell->exposay.padding_inner = 80;
230
231 w = output->width - (shell->exposay.hpadding_outer * 2);
232 w -= shell->exposay.padding_inner * (shell->exposay.grid_size - 1);
233 w /= shell->exposay.grid_size;
234
235 h = output->height - (shell->exposay.vpadding_outer * 2);
236 h -= shell->exposay.padding_inner * (shell->exposay.grid_size - 1);
237 h /= shell->exposay.grid_size;
238
239 shell->exposay.surface_size = (w < h) ? w : h;
240 if (shell->exposay.surface_size > (output->width / 2))
241 shell->exposay.surface_size = output->width / 2;
242 if (shell->exposay.surface_size > (output->height / 2))
243 shell->exposay.surface_size = output->height / 2;
244
245 i = 0;
246 wl_list_for_each(view, &workspace->layer.view_list, layer_link) {
247 int pad;
248
249 pad = shell->exposay.surface_size + shell->exposay.padding_inner;
250
251 if (!get_shell_surface(view->surface))
252 continue;
253
254 esurface = malloc(sizeof(*esurface));
255 if (!esurface) {
256 exposay_set_state(shell, EXPOSAY_TARGET_CANCEL,
257 shell->exposay.seat);
258 break;
259 }
260
261 wl_list_insert(&shell->exposay.surface_list, &esurface->link);
262 esurface->shell = shell;
263 esurface->view = view;
264
265 esurface->row = i / shell->exposay.grid_size;
266 esurface->column = i % shell->exposay.grid_size;
267
268 esurface->x = shell->exposay.hpadding_outer;
269 esurface->x += pad * esurface->column;
270 esurface->y = shell->exposay.vpadding_outer;
271 esurface->y += pad * esurface->row;
272
273 if (esurface->row == shell->exposay.grid_size - 1)
274 esurface->x += (shell->exposay.surface_size + shell->exposay.padding_inner) * last_row_removed / 2;
275
276 if (view->surface->width > view->surface->height)
277 esurface->scale = shell->exposay.surface_size / (float) view->surface->width;
278 else
279 esurface->scale = shell->exposay.surface_size / (float) view->surface->height;
280 esurface->width = view->surface->width * esurface->scale;
281 esurface->height = view->surface->height * esurface->scale;
282
283 if (shell->exposay.focus_current == esurface->view)
Emilio Pozuelo Monforte6bbe5a2014-01-07 16:41:39 +0100284 highlight = esurface;
Kristian Høgsberg1ef23132013-12-04 00:20:01 -0800285
Emilio Pozuelo Monfort1a26f1b2014-01-07 16:41:40 +0100286 set_alpha_if_fullscreen(get_shell_surface(view->surface));
287
Kristian Høgsberg1ef23132013-12-04 00:20:01 -0800288 exposay_animate_in(esurface);
289
290 i++;
291 }
292
Emilio Pozuelo Monforte6bbe5a2014-01-07 16:41:39 +0100293 if (highlight)
294 exposay_highlight_surface(shell, highlight);
295
Kristian Høgsberg1ef23132013-12-04 00:20:01 -0800296 weston_compositor_schedule_repaint(shell->compositor);
297
298 return EXPOSAY_LAYOUT_ANIMATE_TO_OVERVIEW;
299}
300
301static void
302exposay_focus(struct weston_pointer_grab *grab)
303{
304}
305
306static void
307exposay_motion(struct weston_pointer_grab *grab, uint32_t time,
308 wl_fixed_t x, wl_fixed_t y)
309{
310 struct desktop_shell *shell =
311 container_of(grab, struct desktop_shell, exposay.grab_ptr);
312
313 weston_pointer_move(grab->pointer, x, y);
314
315 exposay_pick(shell,
316 wl_fixed_to_int(grab->pointer->x),
317 wl_fixed_to_int(grab->pointer->y));
318}
319
320static void
321exposay_button(struct weston_pointer_grab *grab, uint32_t time, uint32_t button,
322 uint32_t state_w)
323{
324 struct desktop_shell *shell =
325 container_of(grab, struct desktop_shell, exposay.grab_ptr);
326 struct weston_seat *seat = grab->pointer->seat;
327 enum wl_pointer_button_state state = state_w;
328
329 if (button != BTN_LEFT)
330 return;
331
332 /* Store the surface we clicked on, and don't do anything if we end up
333 * releasing on a different surface. */
334 if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
335 shell->exposay.clicked = shell->exposay.focus_current;
336 return;
337 }
338
339 if (shell->exposay.focus_current == shell->exposay.clicked)
340 exposay_set_state(shell, EXPOSAY_TARGET_SWITCH, seat);
341 else
342 shell->exposay.clicked = NULL;
343}
344
345static void
346exposay_pointer_grab_cancel(struct weston_pointer_grab *grab)
347{
348 struct desktop_shell *shell =
349 container_of(grab, struct desktop_shell, exposay.grab_ptr);
350
351 exposay_set_state(shell, EXPOSAY_TARGET_CANCEL, shell->exposay.seat);
352}
353
354static const struct weston_pointer_grab_interface exposay_ptr_grab = {
355 exposay_focus,
356 exposay_motion,
357 exposay_button,
358 exposay_pointer_grab_cancel,
359};
360
361static int
362exposay_maybe_move(struct desktop_shell *shell, int row, int column)
363{
364 struct exposay_surface *esurface;
365
366 wl_list_for_each(esurface, &shell->exposay.surface_list, link) {
367 if (esurface->row != row || esurface->column != column)
368 continue;
369
370 exposay_highlight_surface(shell, esurface);
371 return 1;
372 }
373
374 return 0;
375}
376
377static void
378exposay_key(struct weston_keyboard_grab *grab, uint32_t time, uint32_t key,
379 uint32_t state_w)
380{
381 struct weston_seat *seat = grab->keyboard->seat;
382 struct desktop_shell *shell =
383 container_of(grab, struct desktop_shell, exposay.grab_kbd);
384 enum wl_keyboard_key_state state = state_w;
385
386 if (state != WL_KEYBOARD_KEY_STATE_RELEASED)
387 return;
388
389 switch (key) {
390 case KEY_ESC:
391 exposay_set_state(shell, EXPOSAY_TARGET_CANCEL, seat);
392 break;
393 case KEY_ENTER:
394 exposay_set_state(shell, EXPOSAY_TARGET_SWITCH, seat);
395 break;
396 case KEY_UP:
397 exposay_maybe_move(shell, shell->exposay.row_current - 1,
398 shell->exposay.column_current);
399 break;
400 case KEY_DOWN:
401 /* Special case for trying to move to the bottom row when it
402 * has fewer items than all the others. */
403 if (!exposay_maybe_move(shell, shell->exposay.row_current + 1,
404 shell->exposay.column_current) &&
405 shell->exposay.row_current < (shell->exposay.grid_size - 1)) {
406 exposay_maybe_move(shell, shell->exposay.row_current + 1,
407 (shell->exposay.num_surfaces %
408 shell->exposay.grid_size) - 1);
409 }
410 break;
411 case KEY_LEFT:
412 exposay_maybe_move(shell, shell->exposay.row_current,
413 shell->exposay.column_current - 1);
414 break;
415 case KEY_RIGHT:
416 exposay_maybe_move(shell, shell->exposay.row_current,
417 shell->exposay.column_current + 1);
418 break;
419 case KEY_TAB:
420 /* Try to move right, then down (and to the leftmost column),
421 * then if all else fails, to the top left. */
422 if (!exposay_maybe_move(shell, shell->exposay.row_current,
423 shell->exposay.column_current + 1) &&
424 !exposay_maybe_move(shell, shell->exposay.row_current + 1, 0))
425 exposay_maybe_move(shell, 0, 0);
426 break;
427 default:
428 break;
429 }
430}
431
432static void
433exposay_modifier(struct weston_keyboard_grab *grab, uint32_t serial,
434 uint32_t mods_depressed, uint32_t mods_latched,
435 uint32_t mods_locked, uint32_t group)
436{
437 struct desktop_shell *shell =
438 container_of(grab, struct desktop_shell, exposay.grab_kbd);
439 struct weston_seat *seat = (struct weston_seat *) grab->keyboard->seat;
440
441 /* We want to know when mod has been pressed and released.
442 * FIXME: There is a problem here: if mod is pressed, then a key
443 * is pressed and released, then mod is released, we will treat that
444 * as if only mod had been pressed and released. */
445 if (seat->modifier_state) {
446 if (seat->modifier_state == shell->binding_modifier) {
447 shell->exposay.mod_pressed = true;
448 } else {
449 shell->exposay.mod_invalid = true;
450 }
451 } else {
452 if (shell->exposay.mod_pressed && !shell->exposay.mod_invalid)
453 exposay_set_state(shell, EXPOSAY_TARGET_CANCEL, seat);
454
455 shell->exposay.mod_invalid = false;
456 shell->exposay.mod_pressed = false;
457 }
458
459 return;
460}
461
462static void
463exposay_cancel(struct weston_keyboard_grab *grab)
464{
465 struct desktop_shell *shell =
466 container_of(grab, struct desktop_shell, exposay.grab_kbd);
467
468 exposay_set_state(shell, EXPOSAY_TARGET_CANCEL, shell->exposay.seat);
469}
470
471static const struct weston_keyboard_grab_interface exposay_kbd_grab = {
472 exposay_key,
473 exposay_modifier,
474 exposay_cancel,
475};
476
477/**
478 * Called when the transition from overview -> inactive has completed.
479 */
480static enum exposay_layout_state
481exposay_set_inactive(struct desktop_shell *shell)
482{
483 struct weston_seat *seat = shell->exposay.seat;
484
485 weston_keyboard_end_grab(seat->keyboard);
486 weston_pointer_end_grab(seat->pointer);
487 if (seat->keyboard->input_method_resource)
488 seat->keyboard->grab = &seat->keyboard->input_method_grab;
489
490 return EXPOSAY_LAYOUT_INACTIVE;
491}
492
493/**
494 * Begins the transition from overview to inactive. */
495static enum exposay_layout_state
496exposay_transition_inactive(struct desktop_shell *shell, int switch_focus)
497{
498 struct exposay_surface *esurface;
499
500 /* Call activate() before we start the animations to avoid
501 * animating back the old state and then immediately transitioning
502 * to the new. */
503 if (switch_focus && shell->exposay.focus_current)
504 activate(shell, shell->exposay.focus_current->surface,
505 shell->exposay.seat);
506 else if (shell->exposay.focus_prev)
507 activate(shell, shell->exposay.focus_prev->surface,
508 shell->exposay.seat);
509
510 wl_list_for_each(esurface, &shell->exposay.surface_list, link)
511 exposay_animate_out(esurface);
512 weston_compositor_schedule_repaint(shell->compositor);
513
514 return EXPOSAY_LAYOUT_ANIMATE_TO_INACTIVE;
515}
516
517static enum exposay_layout_state
518exposay_transition_active(struct desktop_shell *shell)
519{
520 struct weston_seat *seat = shell->exposay.seat;
521
522 shell->exposay.workspace = get_current_workspace(shell);
523 shell->exposay.focus_prev = get_default_view (seat->keyboard->focus);
524 shell->exposay.focus_current = get_default_view (seat->keyboard->focus);
525 shell->exposay.clicked = NULL;
526 wl_list_init(&shell->exposay.surface_list);
527
528 lower_fullscreen_layer(shell);
529 shell->exposay.grab_kbd.interface = &exposay_kbd_grab;
530 weston_keyboard_start_grab(seat->keyboard,
531 &shell->exposay.grab_kbd);
532 weston_keyboard_set_focus(seat->keyboard, NULL);
533
534 shell->exposay.grab_ptr.interface = &exposay_ptr_grab;
535 weston_pointer_start_grab(seat->pointer,
536 &shell->exposay.grab_ptr);
537 weston_pointer_set_focus(seat->pointer, NULL,
538 seat->pointer->x, seat->pointer->y);
539
540 return exposay_layout(shell);
541}
542
543static void
544exposay_check_state(struct desktop_shell *shell)
545{
546 enum exposay_layout_state state_new = shell->exposay.state_cur;
547 int do_switch = 0;
548
549 /* Don't do anything whilst animations are running, just store up
550 * target state changes and only act on them when the animations have
551 * completed. */
552 if (exposay_is_animating(shell))
553 return;
554
555 switch (shell->exposay.state_target) {
556 case EXPOSAY_TARGET_OVERVIEW:
557 switch (shell->exposay.state_cur) {
558 case EXPOSAY_LAYOUT_OVERVIEW:
559 goto out;
560 case EXPOSAY_LAYOUT_ANIMATE_TO_OVERVIEW:
561 state_new = EXPOSAY_LAYOUT_OVERVIEW;
562 break;
563 default:
564 state_new = exposay_transition_active(shell);
565 break;
566 }
567 break;
568
569 case EXPOSAY_TARGET_SWITCH:
570 do_switch = 1; /* fallthrough */
571 case EXPOSAY_TARGET_CANCEL:
572 switch (shell->exposay.state_cur) {
573 case EXPOSAY_LAYOUT_INACTIVE:
574 goto out;
575 case EXPOSAY_LAYOUT_ANIMATE_TO_INACTIVE:
576 state_new = exposay_set_inactive(shell);
577 break;
578 default:
579 state_new = exposay_transition_inactive(shell, do_switch);
580 break;
581 }
582
583 break;
584 }
585
586out:
587 shell->exposay.state_cur = state_new;
588}
589
590static void
591exposay_set_state(struct desktop_shell *shell, enum exposay_target_state state,
592 struct weston_seat *seat)
593{
594 shell->exposay.state_target = state;
595 shell->exposay.seat = seat;
596 exposay_check_state(shell);
597}
598
599void
600exposay_binding(struct weston_seat *seat, enum weston_keyboard_modifier modifier,
601 void *data)
602{
603 struct desktop_shell *shell = data;
604
605 exposay_set_state(shell, EXPOSAY_TARGET_OVERVIEW, seat);
606}