From df8133b72585af84b1680c4ba605d088d7592e4c Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 19 Nov 2013 11:37:14 +0100 Subject: [PATCH] Add Exposay Exposay provides window overview functions which, when a key which produces the binding modifier is pressed on its own, scales all currently-open windows down to be shown overlaid on the desktop, providing keyboard and mouse navigation to be able to switch window focus. [pochu: rebased, ported to weston_view] --- src/shell.c | 575 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 575 insertions(+) diff --git a/src/shell.c b/src/shell.c index 9ad36bb..b2bc74a 100644 --- a/src/shell.c +++ b/src/shell.c @@ -1,6 +1,7 @@ /* * Copyright © 2010-2012 Intel Corporation * Copyright © 2011-2012 Collabora, Ltd. + * Copyright © 2013 Raspberry Pi Foundation * * Permission to use, copy, modify, distribute, and sell this software and * its documentation for any purpose is hereby granted without fee, provided @@ -56,6 +57,19 @@ enum fade_type { FADE_OUT }; +enum exposay_target_state { + EXPOSAY_TARGET_OVERVIEW, /* show all windows */ + EXPOSAY_TARGET_CANCEL, /* return to normal, same focus */ + EXPOSAY_TARGET_SWITCH, /* return to normal, switch focus */ +}; + +enum exposay_layout_state { + EXPOSAY_LAYOUT_INACTIVE = 0, /* normal desktop */ + EXPOSAY_LAYOUT_ANIMATE_TO_INACTIVE, /* in transition to normal */ + EXPOSAY_LAYOUT_OVERVIEW, /* show all windows */ + EXPOSAY_LAYOUT_ANIMATE_TO_OVERVIEW, /* in transition to all windows */ +}; + struct focus_state { struct weston_seat *seat; struct workspace *ws; @@ -180,6 +194,34 @@ struct desktop_shell { struct wl_event_source *startup_timer; } fade; + struct exposay { + /* XXX: Make these exposay_surfaces. */ + struct weston_view *focus_prev; + struct weston_view *focus_current; + struct weston_view *clicked; + struct workspace *workspace; + struct weston_seat *seat; + struct wl_list surface_list; + + struct weston_keyboard_grab grab_kbd; + struct weston_pointer_grab grab_ptr; + + enum exposay_target_state state_target; + enum exposay_layout_state state_cur; + int in_flight; /* number of animations still running */ + + int num_surfaces; + int grid_size; + int surface_size; + + int hpadding_outer; + int vpadding_outer; + int padding_inner; + + int row_current; + int column_current; + } exposay; + uint32_t binding_modifier; enum animation_type win_animation_type; enum animation_type startup_animation_type; @@ -4675,6 +4717,534 @@ switcher_binding(struct weston_seat *seat, uint32_t time, uint32_t key, switcher_next(switcher); } +struct exposay_surface { + struct desktop_shell *shell; + struct weston_surface *surface; + struct weston_view *view; + struct wl_list link; + + int x; + int y; + int width; + int height; + double scale; + + int row; + int column; + + /* The animations only apply a transformation for their own lifetime, + * and don't have an option to indefinitely maintain the + * transformation in a steady state - so, we apply our own once the + * animation has finished. */ + struct weston_transform transform; +}; + +static void exposay_set_state(struct desktop_shell *shell, + enum exposay_target_state state, + struct weston_seat *seat); +static void exposay_check_state(struct desktop_shell *shell); + +static void +exposay_in_flight_inc(struct desktop_shell *shell) +{ + shell->exposay.in_flight++; +} + +static void +exposay_in_flight_dec(struct desktop_shell *shell) +{ + if (--shell->exposay.in_flight > 0) + return; + + exposay_check_state(shell); +} + +static void +exposay_animate_in_done(struct weston_view_animation *animation, void *data) +{ + struct exposay_surface *esurface = data; + + wl_list_insert(&esurface->view->geometry.transformation_list, + &esurface->transform.link); + weston_matrix_init(&esurface->transform.matrix); + weston_matrix_scale(&esurface->transform.matrix, + esurface->scale, esurface->scale, 1.0f); + weston_matrix_translate(&esurface->transform.matrix, + esurface->x - esurface->view->geometry.x, + esurface->y - esurface->view->geometry.y, + 0); + + weston_view_geometry_dirty(esurface->view); + weston_compositor_schedule_repaint(esurface->view->surface->compositor); + + exposay_in_flight_dec(esurface->shell); +} + +static void +exposay_animate_in(struct exposay_surface *esurface) +{ + exposay_in_flight_inc(esurface->shell); + + weston_move_scale_run(esurface->view, + esurface->x - esurface->view->geometry.x, + esurface->y - esurface->view->geometry.y, + 1.0, esurface->scale, 0, + exposay_animate_in_done, esurface); +} + +static void +exposay_animate_out_done(struct weston_view_animation *animation, void *data) +{ + struct exposay_surface *esurface = data; + struct desktop_shell *shell = esurface->shell; + + wl_list_remove(&esurface->link); + free(esurface); + + exposay_in_flight_dec(shell); +} + +static void +exposay_animate_out(struct exposay_surface *esurface) +{ + exposay_in_flight_inc(esurface->shell); + + /* Remove the static transformation set up by + * exposay_transform_in_done(). */ + wl_list_remove(&esurface->transform.link); + weston_view_geometry_dirty(esurface->view); + + weston_move_scale_run(esurface->view, + esurface->x - esurface->view->geometry.x, + esurface->y - esurface->view->geometry.y, + 1.0, esurface->scale, 1, + exposay_animate_out_done, esurface); +} + +static void +exposay_highlight_surface(struct desktop_shell *shell, + struct exposay_surface *esurface) +{ + struct weston_view *view = NULL; + + if (esurface) { + shell->exposay.row_current = esurface->row; + shell->exposay.column_current = esurface->column; + view = esurface->view; + } + + animate_focus_change(shell, shell->exposay.workspace, + shell->exposay.focus_current, view); + shell->exposay.focus_current = view; +} + +static int +exposay_is_animating(struct desktop_shell *shell) +{ + if (shell->exposay.state_cur == EXPOSAY_LAYOUT_INACTIVE || + shell->exposay.state_cur == EXPOSAY_LAYOUT_OVERVIEW) + return 0; + + return (shell->exposay.in_flight > 0); +} + +static void +exposay_pick(struct desktop_shell *shell, int x, int y) +{ + struct exposay_surface *esurface; + + if (exposay_is_animating(shell)) + return; + + wl_list_for_each(esurface, &shell->exposay.surface_list, link) { + if (x < esurface->x || x > esurface->x + esurface->width) + continue; + if (y < esurface->y || y > esurface->y + esurface->height) + continue; + + exposay_highlight_surface(shell, esurface); + return; + } +} + +/* Pretty lame layout for now; just tries to make a square. Should take + * aspect ratio into account really. Also needs to be notified of surface + * addition and removal and adjust layout/animate accordingly. */ +static enum exposay_layout_state +exposay_layout(struct desktop_shell *shell) +{ + struct workspace *workspace = shell->exposay.workspace; + struct weston_compositor *compositor = shell->compositor; + struct weston_output *output = get_default_output(compositor); + struct weston_view *view; + struct exposay_surface *esurface; + int w, h; + int i; + int last_row_removed = 0; + + wl_list_init(&shell->exposay.surface_list); + + shell->exposay.num_surfaces = 0; + wl_list_for_each(view, &workspace->layer.view_list, layer_link) { + if (!get_shell_surface(view->surface)) + continue; + shell->exposay.num_surfaces++; + } + + if (shell->exposay.num_surfaces == 0) { + shell->exposay.grid_size = 0; + shell->exposay.hpadding_outer = 0; + shell->exposay.vpadding_outer = 0; + shell->exposay.padding_inner = 0; + shell->exposay.surface_size = 0; + return EXPOSAY_LAYOUT_OVERVIEW; + } + + /* Lay the grid out as square as possible, losing surfaces from the + * bottom row if required. Start with fixed padding of a 10% margin + * around the outside and 80px internal padding between surfaces, and + * maximise the area made available to surfaces after this, but only + * to a maximum of 1/3rd the total output size. + * + * If we can't make a square grid, add one extra row at the bottom + * which will have a smaller number of columns. + * + * XXX: Surely there has to be a better way to express this maths, + * right?! + */ + shell->exposay.grid_size = floor(sqrtf(shell->exposay.num_surfaces)); + if (pow(shell->exposay.grid_size, 2) != shell->exposay.num_surfaces) + shell->exposay.grid_size++; + last_row_removed = pow(shell->exposay.grid_size, 2) - shell->exposay.num_surfaces; + + shell->exposay.hpadding_outer = (output->width / 10); + shell->exposay.vpadding_outer = (output->height / 10); + shell->exposay.padding_inner = 80; + + w = output->width - (shell->exposay.hpadding_outer * 2); + w -= shell->exposay.padding_inner * (shell->exposay.grid_size - 1); + w /= shell->exposay.grid_size; + + h = output->height - (shell->exposay.vpadding_outer * 2); + h -= shell->exposay.padding_inner * (shell->exposay.grid_size - 1); + h /= shell->exposay.grid_size; + + shell->exposay.surface_size = (w < h) ? w : h; + if (shell->exposay.surface_size > (output->width / 2)) + shell->exposay.surface_size = output->width / 2; + if (shell->exposay.surface_size > (output->height / 2)) + shell->exposay.surface_size = output->height / 2; + + i = 0; + wl_list_for_each(view, &workspace->layer.view_list, layer_link) { + int pad; + + pad = shell->exposay.surface_size + shell->exposay.padding_inner; + + if (!get_shell_surface(view->surface)) + continue; + + esurface = malloc(sizeof(*esurface)); + if (!esurface) { + exposay_set_state(shell, EXPOSAY_TARGET_CANCEL, + shell->exposay.seat); + break; + } + + wl_list_insert(&shell->exposay.surface_list, &esurface->link); + esurface->shell = shell; + esurface->view = view; + + esurface->row = i / shell->exposay.grid_size; + esurface->column = i % shell->exposay.grid_size; + + esurface->x = shell->exposay.hpadding_outer; + esurface->x += pad * esurface->column; + esurface->y = shell->exposay.vpadding_outer; + esurface->y += pad * esurface->row; + + if (esurface->row == shell->exposay.grid_size - 1) + esurface->x += (shell->exposay.surface_size + shell->exposay.padding_inner) * last_row_removed / 2; + + if (view->geometry.width > view->geometry.height) + esurface->scale = shell->exposay.surface_size / (float) view->geometry.width; + else + esurface->scale = shell->exposay.surface_size / (float) view->geometry.height; + esurface->width = view->geometry.width * esurface->scale; + esurface->height = view->geometry.height * esurface->scale; + + if (shell->exposay.focus_current == esurface->view) + exposay_highlight_surface(shell, esurface); + + exposay_animate_in(esurface); + + i++; + } + + weston_compositor_schedule_repaint(shell->compositor); + + return EXPOSAY_LAYOUT_ANIMATE_TO_OVERVIEW; +} + +static void +exposay_motion(struct weston_pointer_grab *grab, uint32_t time) +{ + struct desktop_shell *shell = + container_of(grab, struct desktop_shell, exposay.grab_ptr); + + exposay_pick(shell, + wl_fixed_to_int(grab->pointer->x), + wl_fixed_to_int(grab->pointer->y)); +} + +static void +exposay_button(struct weston_pointer_grab *grab, uint32_t time, uint32_t button, + uint32_t state_w) +{ + struct desktop_shell *shell = + container_of(grab, struct desktop_shell, exposay.grab_ptr); + struct weston_seat *seat = grab->pointer->seat; + enum wl_pointer_button_state state = state_w; + + if (button != BTN_LEFT) + return; + + /* Store the surface we clicked on, and don't do anything if we end up + * releasing on a different surface. */ + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + shell->exposay.clicked = shell->exposay.focus_current; + return; + } + + if (shell->exposay.focus_current == shell->exposay.clicked) + exposay_set_state(shell, EXPOSAY_TARGET_SWITCH, seat); + else + shell->exposay.clicked = NULL; +} + +static const struct weston_pointer_grab_interface exposay_ptr_grab = { + noop_grab_focus, + exposay_motion, + exposay_button, +}; + +static int +exposay_maybe_move(struct desktop_shell *shell, int row, int column) +{ + struct exposay_surface *esurface; + + wl_list_for_each(esurface, &shell->exposay.surface_list, link) { + if (esurface->row != row || esurface->column != column) + continue; + + exposay_highlight_surface(shell, esurface); + return 1; + } + + return 0; +} + +static void +exposay_key(struct weston_keyboard_grab *grab, uint32_t time, uint32_t key, + uint32_t state_w) +{ + struct weston_seat *seat = grab->keyboard->seat; + struct desktop_shell *shell = + container_of(grab, struct desktop_shell, exposay.grab_kbd); + enum wl_keyboard_key_state state = state_w; + + if (state != WL_KEYBOARD_KEY_STATE_RELEASED) + return; + + switch (key) { + case KEY_ESC: + exposay_set_state(shell, EXPOSAY_TARGET_CANCEL, seat); + break; + case KEY_ENTER: + exposay_set_state(shell, EXPOSAY_TARGET_SWITCH, seat); + break; + case KEY_UP: + exposay_maybe_move(shell, shell->exposay.row_current - 1, + shell->exposay.column_current); + break; + case KEY_DOWN: + /* Special case for trying to move to the bottom row when it + * has fewer items than all the others. */ + if (!exposay_maybe_move(shell, shell->exposay.row_current + 1, + shell->exposay.column_current) && + shell->exposay.row_current < (shell->exposay.grid_size - 1)) { + exposay_maybe_move(shell, shell->exposay.row_current + 1, + (shell->exposay.num_surfaces % + shell->exposay.grid_size) - 1); + } + break; + case KEY_LEFT: + exposay_maybe_move(shell, shell->exposay.row_current, + shell->exposay.column_current - 1); + break; + case KEY_RIGHT: + exposay_maybe_move(shell, shell->exposay.row_current, + shell->exposay.column_current + 1); + break; + case KEY_TAB: + /* Try to move right, then down (and to the leftmost column), + * then if all else fails, to the top left. */ + if (!exposay_maybe_move(shell, shell->exposay.row_current, + shell->exposay.column_current + 1) && + !exposay_maybe_move(shell, shell->exposay.row_current + 1, 0)) + exposay_maybe_move(shell, 0, 0); + break; + default: + break; + } +} + +static void +exposay_modifier(struct weston_keyboard_grab *grab, uint32_t serial, + uint32_t mods_depressed, uint32_t mods_latched, + uint32_t mods_locked, uint32_t group) +{ +} + +static const struct weston_keyboard_grab_interface exposay_kbd_grab = { + exposay_key, + exposay_modifier, +}; + +/** + * Called when the transition from overview -> inactive has completed. + */ +static enum exposay_layout_state +exposay_set_inactive(struct desktop_shell *shell) +{ + struct weston_seat *seat = shell->exposay.seat; + + weston_keyboard_end_grab(seat->keyboard); + weston_pointer_end_grab(seat->pointer); + if (seat->keyboard->input_method_resource) + seat->keyboard->grab = &seat->keyboard->input_method_grab; + + return EXPOSAY_LAYOUT_INACTIVE; +} + +/** + * Begins the transition from overview to inactive. */ +static enum exposay_layout_state +exposay_transition_inactive(struct desktop_shell *shell, int switch_focus) +{ + struct exposay_surface *esurface; + + /* Call activate() before we start the animations to avoid + * animating back the old state and then immediately transitioning + * to the new. */ + if (switch_focus && shell->exposay.focus_current) + activate(shell, shell->exposay.focus_current->surface, + shell->exposay.seat); + else if (shell->exposay.focus_prev) + activate(shell, shell->exposay.focus_prev->surface, + shell->exposay.seat); + + wl_list_for_each(esurface, &shell->exposay.surface_list, link) + exposay_animate_out(esurface); + weston_compositor_schedule_repaint(shell->compositor); + + return EXPOSAY_LAYOUT_ANIMATE_TO_INACTIVE; +} + +static enum exposay_layout_state +exposay_transition_active(struct desktop_shell *shell) +{ + struct weston_seat *seat = shell->exposay.seat; + + shell->exposay.workspace = get_current_workspace(shell); + shell->exposay.focus_prev = get_default_view (seat->keyboard->focus); + shell->exposay.focus_current = get_default_view (seat->keyboard->focus); + shell->exposay.clicked = NULL; + wl_list_init(&shell->exposay.surface_list); + + lower_fullscreen_layer(shell); + shell->exposay.grab_kbd.interface = &exposay_kbd_grab; + weston_keyboard_start_grab(seat->keyboard, + &shell->exposay.grab_kbd); + weston_keyboard_set_focus(seat->keyboard, NULL); + + shell->exposay.grab_ptr.interface = &exposay_ptr_grab; + weston_pointer_start_grab(seat->pointer, + &shell->exposay.grab_ptr); + weston_pointer_set_focus(seat->pointer, NULL, + seat->pointer->x, seat->pointer->y); + + return exposay_layout(shell); +} + +static void +exposay_check_state(struct desktop_shell *shell) +{ + enum exposay_layout_state state_new = shell->exposay.state_cur; + int do_switch = 0; + + /* Don't do anything whilst animations are running, just store up + * target state changes and only act on them when the animations have + * completed. */ + if (exposay_is_animating(shell)) + return; + + switch (shell->exposay.state_target) { + case EXPOSAY_TARGET_OVERVIEW: + switch (shell->exposay.state_cur) { + case EXPOSAY_LAYOUT_OVERVIEW: + goto out; + case EXPOSAY_LAYOUT_ANIMATE_TO_OVERVIEW: + state_new = EXPOSAY_LAYOUT_OVERVIEW; + break; + default: + state_new = exposay_transition_active(shell); + break; + } + break; + + case EXPOSAY_TARGET_SWITCH: + do_switch = 1; /* fallthrough */ + case EXPOSAY_TARGET_CANCEL: + switch (shell->exposay.state_cur) { + case EXPOSAY_LAYOUT_INACTIVE: + goto out; + case EXPOSAY_LAYOUT_ANIMATE_TO_INACTIVE: + state_new = exposay_set_inactive(shell); + break; + default: + state_new = exposay_transition_inactive(shell, do_switch); + break; + } + + break; + } + +out: + shell->exposay.state_cur = state_new; +} + +static void +exposay_set_state(struct desktop_shell *shell, enum exposay_target_state state, + struct weston_seat *seat) +{ + shell->exposay.state_target = state; + shell->exposay.seat = seat; + exposay_check_state(shell); +} + +static void +exposay_binding(struct weston_seat *seat, enum weston_keyboard_modifier modifier, + void *data) +{ + struct desktop_shell *shell = data; + + if (shell->exposay.state_target == EXPOSAY_TARGET_OVERVIEW) + exposay_set_state(shell, EXPOSAY_TARGET_CANCEL, seat); + else + exposay_set_state(shell, EXPOSAY_TARGET_OVERVIEW, seat); +} + static void backlight_binding(struct weston_seat *seat, uint32_t time, uint32_t key, void *data) @@ -5088,6 +5658,8 @@ shell_add_bindings(struct weston_compositor *ec, struct desktop_shell *shell) workspace_move_surface_down_binding, shell); + weston_compositor_add_modifier_binding(ec, mod, exposay_binding, shell); + /* Add bindings for mod+F[1-6] for workspace 1 to 6. */ if (shell->workspaces.num > 1) { num_workspace_bindings = shell->workspaces.num; @@ -5157,6 +5729,9 @@ module_init(struct weston_compositor *ec, shell_configuration(shell); + shell->exposay.state_cur = EXPOSAY_LAYOUT_INACTIVE; + shell->exposay.state_target = EXPOSAY_TARGET_CANCEL; + for (i = 0; i < shell->workspaces.num; i++) { pws = wl_array_add(&shell->workspaces.array, sizeof *pws); if (pws == NULL) -- 2.7.4