diff options
Diffstat (limited to 'fullscreen-shell/fullscreen-shell.c')
-rw-r--r-- | fullscreen-shell/fullscreen-shell.c | 830 |
1 files changed, 830 insertions, 0 deletions
diff --git a/fullscreen-shell/fullscreen-shell.c b/fullscreen-shell/fullscreen-shell.c new file mode 100644 index 00000000..a8dad8e6 --- /dev/null +++ b/fullscreen-shell/fullscreen-shell.c @@ -0,0 +1,830 @@ +/* + * Copyright © 2013 Jason Ekstrand + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include <sys/wait.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> + +#include "compositor.h" +#include "fullscreen-shell-server-protocol.h" + +struct fullscreen_shell { + struct wl_client *client; + struct wl_listener client_destroyed; + struct weston_compositor *compositor; + + struct weston_layer layer; + struct wl_list output_list; + struct wl_listener output_created_listener; + + struct wl_listener seat_created_listener; +}; + +struct fs_output { + struct fullscreen_shell *shell; + struct wl_list link; + + struct weston_output *output; + struct wl_listener output_destroyed; + + struct { + struct weston_surface *surface; + struct wl_listener surface_destroyed; + struct wl_resource *mode_feedback; + + int presented_for_mode; + enum _wl_fullscreen_shell_present_method method; + int32_t framerate; + } pending; + + struct weston_surface *surface; + struct wl_listener surface_destroyed; + struct weston_view *view; + struct weston_view *black_view; + struct weston_transform transform; /* matrix from x, y */ + + int presented_for_mode; + enum _wl_fullscreen_shell_present_method method; + uint32_t framerate; +}; + +struct pointer_focus_listener { + struct fullscreen_shell *shell; + struct wl_listener pointer_focus; + struct wl_listener seat_caps; + struct wl_listener seat_destroyed; +}; + +static void +pointer_focus_changed(struct wl_listener *listener, void *data) +{ + struct weston_pointer *pointer = data; + + if (pointer->focus && pointer->focus->surface->resource) + weston_surface_activate(pointer->focus->surface, pointer->seat); +} + +static void +seat_caps_changed(struct wl_listener *l, void *data) +{ + struct weston_seat *seat = data; + struct pointer_focus_listener *listener; + struct fs_output *fsout; + + listener = container_of(l, struct pointer_focus_listener, seat_caps); + + /* no pointer */ + if (seat->pointer) { + if (!listener->pointer_focus.link.prev) { + wl_signal_add(&seat->pointer->focus_signal, + &listener->pointer_focus); + } + } else { + if (listener->pointer_focus.link.prev) { + wl_list_remove(&listener->pointer_focus.link); + } + } + + if (seat->keyboard && seat->keyboard->focus != NULL) { + wl_list_for_each(fsout, &listener->shell->output_list, link) { + if (fsout->surface) { + weston_surface_activate(fsout->surface, seat); + return; + } + } + } +} + +static void +seat_destroyed(struct wl_listener *l, void *data) +{ + struct pointer_focus_listener *listener; + + listener = container_of(l, struct pointer_focus_listener, + seat_destroyed); + + free(listener); +} + +static void +seat_created(struct wl_listener *l, void *data) +{ + struct weston_seat *seat = data; + struct pointer_focus_listener *listener; + + listener = malloc(sizeof *listener); + if (!listener) + return; + memset(listener, 0, sizeof *listener); + + listener->shell = container_of(l, struct fullscreen_shell, + seat_created_listener); + listener->pointer_focus.notify = pointer_focus_changed; + listener->seat_caps.notify = seat_caps_changed; + listener->seat_destroyed.notify = seat_destroyed; + + wl_signal_add(&seat->destroy_signal, &listener->seat_destroyed); + wl_signal_add(&seat->updated_caps_signal, &listener->seat_caps); + + seat_caps_changed(&listener->seat_caps, seat); +} + +static void +black_surface_configure(struct weston_surface *es, int32_t sx, int32_t sy) +{ +} + +static struct weston_view * +create_black_surface(struct weston_compositor *ec, struct fs_output *fsout, + float x, float y, int w, int h) +{ + struct weston_surface *surface = NULL; + struct weston_view *view; + + surface = weston_surface_create(ec); + if (surface == NULL) { + weston_log("no memory\n"); + return NULL; + } + view = weston_view_create(surface); + if (!view) { + weston_surface_destroy(surface); + weston_log("no memory\n"); + return NULL; + } + + surface->configure = black_surface_configure; + surface->configure_private = fsout; + weston_surface_set_color(surface, 0.0f, 0.0f, 0.0f, 1.0f); + pixman_region32_fini(&surface->opaque); + pixman_region32_init_rect(&surface->opaque, 0, 0, w, h); + pixman_region32_fini(&surface->input); + pixman_region32_init_rect(&surface->input, 0, 0, w, h); + + weston_surface_set_size(surface, w, h); + weston_view_set_position(view, x, y); + + return view; +} + +static void +fs_output_set_surface(struct fs_output *fsout, struct weston_surface *surface, + enum _wl_fullscreen_shell_present_method method, + int32_t framerate, int presented_for_mode); +static void +fs_output_apply_pending(struct fs_output *fsout); +static void +fs_output_clear_pending(struct fs_output *fsout); + +static void +fs_output_destroy(struct fs_output *fsout) +{ + fs_output_set_surface(fsout, NULL, 0, 0, 0); + fs_output_clear_pending(fsout); + + wl_list_remove(&fsout->link); + + if (fsout->output) + wl_list_remove(&fsout->output_destroyed.link); +} + +static void +output_destroyed(struct wl_listener *listener, void *data) +{ + struct fs_output *output = container_of(listener, + struct fs_output, + output_destroyed); + fs_output_destroy(output); +} + +static void +surface_destroyed(struct wl_listener *listener, void *data) +{ + struct fs_output *fsout = container_of(listener, + struct fs_output, + surface_destroyed); + fsout->surface = NULL; + fsout->view = NULL; +} + +static void +pending_surface_destroyed(struct wl_listener *listener, void *data) +{ + struct fs_output *fsout = container_of(listener, + struct fs_output, + pending.surface_destroyed); + fsout->pending.surface = NULL; +} + +static struct fs_output * +fs_output_create(struct fullscreen_shell *shell, struct weston_output *output) +{ + struct fs_output *fsout; + + fsout = malloc(sizeof *fsout); + if (!fsout) + return NULL; + memset(fsout, 0, sizeof *fsout); + + fsout->shell = shell; + wl_list_insert(&shell->output_list, &fsout->link); + + fsout->output = output; + fsout->output_destroyed.notify = output_destroyed; + wl_signal_add(&output->destroy_signal, &fsout->output_destroyed); + + fsout->surface_destroyed.notify = surface_destroyed; + fsout->pending.surface_destroyed.notify = pending_surface_destroyed; + fsout->black_view = create_black_surface(shell->compositor, fsout, + output->x, output->y, + output->width, output->height); + wl_list_insert(&shell->layer.view_list, + &fsout->black_view->layer_link); + wl_list_init(&fsout->transform.link); + return fsout; +} + +static struct fs_output * +fs_output_for_output(struct weston_output *output) +{ + struct wl_listener *listener; + + if (!output) + return NULL; + + listener = wl_signal_get(&output->destroy_signal, output_destroyed); + + return container_of(listener, struct fs_output, output_destroyed); +} + +static void +restore_output_mode(struct weston_output *output) +{ + if (output->current_mode != output->original_mode || + (int32_t)output->current_scale != output->original_scale) + weston_output_switch_mode(output, + output->original_mode, + output->original_scale, + WESTON_MODE_SWITCH_RESTORE_NATIVE); +} + +/* + * Returns the bounding box of a surface and all its sub-surfaces, + * in the surface coordinates system. */ +static void +surface_subsurfaces_boundingbox(struct weston_surface *surface, int32_t *x, + int32_t *y, int32_t *w, int32_t *h) { + pixman_region32_t region; + pixman_box32_t *box; + struct weston_subsurface *subsurface; + + pixman_region32_init_rect(®ion, 0, 0, + surface->width, + surface->height); + + wl_list_for_each(subsurface, &surface->subsurface_list, parent_link) { + pixman_region32_union_rect(®ion, ®ion, + subsurface->position.x, + subsurface->position.y, + subsurface->surface->width, + subsurface->surface->height); + } + + box = pixman_region32_extents(®ion); + if (x) + *x = box->x1; + if (y) + *y = box->y1; + if (w) + *w = box->x2 - box->x1; + if (h) + *h = box->y2 - box->y1; + + pixman_region32_fini(®ion); +} + +static void +fs_output_center_view(struct fs_output *fsout) +{ + int32_t surf_x, surf_y, surf_width, surf_height; + float x, y; + struct weston_output *output = fsout->output; + + surface_subsurfaces_boundingbox(fsout->view->surface, &surf_x, &surf_y, + &surf_width, &surf_height); + + x = output->x + (output->width - surf_width) / 2 - surf_x / 2; + y = output->y + (output->height - surf_height) / 2 - surf_y / 2; + + weston_view_set_position(fsout->view, x, y); +} + +static void +fs_output_scale_view(struct fs_output *fsout, float width, float height) +{ + float x, y; + int32_t surf_x, surf_y, surf_width, surf_height; + struct weston_matrix *matrix; + struct weston_view *view = fsout->view; + struct weston_output *output = fsout->output; + + surface_subsurfaces_boundingbox(view->surface, &surf_x, &surf_y, + &surf_width, &surf_height); + + if (output->width == surf_width && output->height == surf_height) { + weston_view_set_position(view, + fsout->output->x - surf_x, + fsout->output->y - surf_y); + } else { + matrix = &fsout->transform.matrix; + weston_matrix_init(matrix); + + weston_matrix_scale(matrix, width / surf_width, + height / surf_height, 1); + wl_list_remove(&fsout->transform.link); + wl_list_insert(&fsout->view->geometry.transformation_list, + &fsout->transform.link); + + x = output->x + (output->width - width) / 2 - surf_x; + y = output->y + (output->height - height) / 2 - surf_y; + + weston_view_set_position(view, x, y); + } +} + +static void +fs_output_configure(struct fs_output *fsout, struct weston_surface *surface); + +static void +fs_output_configure_simple(struct fs_output *fsout, + struct weston_surface *configured_surface) +{ + struct weston_output *output = fsout->output; + float output_aspect, surface_aspect; + int32_t surf_x, surf_y, surf_width, surf_height; + + if (fsout->pending.surface == configured_surface) + fs_output_apply_pending(fsout); + + assert(fsout->view); + + restore_output_mode(fsout->output); + + wl_list_remove(&fsout->transform.link); + wl_list_init(&fsout->transform.link); + + surface_subsurfaces_boundingbox(fsout->view->surface, + &surf_x, &surf_y, + &surf_width, &surf_height); + + output_aspect = (float) output->width / (float) output->height; + surface_aspect = (float) surf_width / (float) surf_height; + + switch (fsout->method) { + case _WL_FULLSCREEN_SHELL_PRESENT_METHOD_DEFAULT: + case _WL_FULLSCREEN_SHELL_PRESENT_METHOD_CENTER: + fs_output_center_view(fsout); + break; + + case _WL_FULLSCREEN_SHELL_PRESENT_METHOD_ZOOM: + if (output_aspect < surface_aspect) + fs_output_scale_view(fsout, + output->width, + output->width / surface_aspect); + else + fs_output_scale_view(fsout, + output->height * surface_aspect, + output->height); + break; + + case _WL_FULLSCREEN_SHELL_PRESENT_METHOD_ZOOM_CROP: + if (output_aspect < surface_aspect) + fs_output_scale_view(fsout, + output->height * surface_aspect, + output->height); + else + fs_output_scale_view(fsout, + output->width, + output->width / surface_aspect); + break; + + case _WL_FULLSCREEN_SHELL_PRESENT_METHOD_STRETCH: + fs_output_scale_view(fsout, output->width, output->height); + break; + default: + break; + } + + weston_view_set_position(fsout->black_view, + fsout->output->x - surf_x, + fsout->output->y - surf_y); + weston_surface_set_size(fsout->black_view->surface, + fsout->output->width, + fsout->output->height); +} + +static void +fs_output_configure_for_mode(struct fs_output *fsout, + struct weston_surface *configured_surface) +{ + int32_t surf_x, surf_y, surf_width, surf_height; + struct weston_mode mode; + int ret; + + if (fsout->pending.surface != configured_surface) { + /* Nothing to really reconfigure. We'll just recenter the + * view in case they played with subsurfaces */ + fs_output_center_view(fsout); + return; + } + + /* We have a pending surface */ + surface_subsurfaces_boundingbox(fsout->pending.surface, + &surf_x, &surf_y, + &surf_width, &surf_height); + + mode.flags = 0; + mode.width = surf_width * fsout->output->native_scale; + mode.height = surf_height * fsout->output->native_scale; + mode.refresh = fsout->pending.framerate; + + ret = weston_output_switch_mode(fsout->output, &mode, + fsout->output->native_scale, + WESTON_MODE_SWITCH_SET_TEMPORARY); + + if (ret != 0) { + /* The mode switch failed. Clear the pending and + * reconfigure as per normal */ + if (fsout->pending.mode_feedback) { + _wl_fullscreen_shell_mode_feedback_send_mode_failed( + fsout->pending.mode_feedback); + wl_resource_destroy(fsout->pending.mode_feedback); + fsout->pending.mode_feedback = NULL; + } + + fs_output_clear_pending(fsout); + return; + } + + if (fsout->pending.mode_feedback) { + _wl_fullscreen_shell_mode_feedback_send_mode_successful( + fsout->pending.mode_feedback); + wl_resource_destroy(fsout->pending.mode_feedback); + fsout->pending.mode_feedback = NULL; + } + + fs_output_apply_pending(fsout); + + weston_view_set_position(fsout->view, + fsout->output->x - surf_x, + fsout->output->y - surf_y); +} + +static void +fs_output_configure(struct fs_output *fsout, + struct weston_surface *surface) +{ + if (fsout->pending.surface == surface) { + if (fsout->pending.presented_for_mode) + fs_output_configure_for_mode(fsout, surface); + else + fs_output_configure_simple(fsout, surface); + } else { + if (fsout->presented_for_mode) + fs_output_configure_for_mode(fsout, surface); + else + fs_output_configure_simple(fsout, surface); + } + + weston_output_schedule_repaint(fsout->output); +} + +static void +configure_presented_surface(struct weston_surface *surface, int32_t sx, + int32_t sy) +{ + struct fullscreen_shell *shell = surface->configure_private; + struct fs_output *fsout; + + if (surface->configure != configure_presented_surface) + return; + + wl_list_for_each(fsout, &shell->output_list, link) + if (fsout->surface == surface || + fsout->pending.surface == surface) + fs_output_configure(fsout, surface); +} + +static void +fs_output_apply_pending(struct fs_output *fsout) +{ + assert(fsout->pending.surface); + + if (fsout->surface && fsout->surface != fsout->pending.surface) { + wl_list_remove(&fsout->surface_destroyed.link); + + weston_view_destroy(fsout->view); + fsout->view = NULL; + + if (wl_list_empty(&fsout->surface->views)) { + fsout->surface->configure = NULL; + fsout->surface->configure_private = NULL; + } + + fsout->surface = NULL; + } + + fsout->method = fsout->pending.method; + fsout->framerate = fsout->pending.framerate; + fsout->presented_for_mode = fsout->pending.presented_for_mode; + + if (fsout->surface != fsout->pending.surface) { + fsout->surface = fsout->pending.surface; + + fsout->view = weston_view_create(fsout->surface); + if (!fsout->view) { + weston_log("no memory\n"); + return; + } + + wl_signal_add(&fsout->surface->destroy_signal, + &fsout->surface_destroyed); + wl_list_insert(&fsout->shell->layer.view_list, + &fsout->view->layer_link); + } + + fs_output_clear_pending(fsout); +} + +static void +fs_output_clear_pending(struct fs_output *fsout) +{ + if (!fsout->pending.surface) + return; + + if (fsout->pending.mode_feedback) { + _wl_fullscreen_shell_mode_feedback_send_present_cancelled( + fsout->pending.mode_feedback); + wl_resource_destroy(fsout->pending.mode_feedback); + fsout->pending.mode_feedback = NULL; + } + + wl_list_remove(&fsout->pending.surface_destroyed.link); + fsout->pending.surface = NULL; +} + +static void +fs_output_set_surface(struct fs_output *fsout, struct weston_surface *surface, + enum _wl_fullscreen_shell_present_method method, + int32_t framerate, int presented_for_mode) +{ + fs_output_clear_pending(fsout); + + if (surface) { + if (!surface->configure) { + surface->configure = configure_presented_surface; + surface->configure_private = fsout->shell; + } + + fsout->pending.surface = surface; + wl_signal_add(&fsout->pending.surface->destroy_signal, + &fsout->pending.surface_destroyed); + + fsout->pending.method = method; + fsout->pending.framerate = framerate; + fsout->pending.presented_for_mode = presented_for_mode; + } else if (fsout->surface) { + /* we clear immediately */ + wl_list_remove(&fsout->surface_destroyed.link); + + weston_view_destroy(fsout->view); + fsout->view = NULL; + + if (wl_list_empty(&fsout->surface->views)) { + fsout->surface->configure = NULL; + fsout->surface->configure_private = NULL; + } + + fsout->surface = NULL; + + weston_output_schedule_repaint(fsout->output); + } +} + +static void +fullscreen_shell_release(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +fullscreen_shell_present_surface(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface_res, + uint32_t method, + struct wl_resource *output_res) +{ + struct fullscreen_shell *shell = + wl_resource_get_user_data(resource); + struct weston_output *output; + struct weston_surface *surface; + struct weston_seat *seat; + struct fs_output *fsout; + + surface = surface_res ? wl_resource_get_user_data(surface_res) : NULL; + + switch(method) { + case _WL_FULLSCREEN_SHELL_PRESENT_METHOD_DEFAULT: + case _WL_FULLSCREEN_SHELL_PRESENT_METHOD_CENTER: + case _WL_FULLSCREEN_SHELL_PRESENT_METHOD_ZOOM: + case _WL_FULLSCREEN_SHELL_PRESENT_METHOD_ZOOM_CROP: + case _WL_FULLSCREEN_SHELL_PRESENT_METHOD_STRETCH: + break; + default: + wl_resource_post_error(resource, + _WL_FULLSCREEN_SHELL_ERROR_INVALID_METHOD, + "Invalid presentation method"); + } + + if (output_res) { + output = wl_resource_get_user_data(output_res); + fsout = fs_output_for_output(output); + fs_output_set_surface(fsout, surface, method, 0, 0); + } else { + wl_list_for_each(fsout, &shell->output_list, link) + fs_output_set_surface(fsout, surface, method, 0, 0); + } + + if (surface) { + wl_list_for_each(seat, &shell->compositor->seat_list, link) { + if (seat->keyboard && seat->keyboard->focus == NULL) + weston_surface_activate(surface, seat); + } + } +} + +static void +mode_feedback_destroyed(struct wl_resource *resource) +{ + struct fs_output *fsout = wl_resource_get_user_data(resource); + + fsout->pending.mode_feedback = NULL; +} + +static void +fullscreen_shell_present_surface_for_mode(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface_res, + struct wl_resource *output_res, + int32_t framerate, + uint32_t feedback_id) +{ + struct fullscreen_shell *shell = + wl_resource_get_user_data(resource); + struct weston_output *output; + struct weston_surface *surface; + struct weston_seat *seat; + struct fs_output *fsout; + + output = wl_resource_get_user_data(output_res); + fsout = fs_output_for_output(output); + + if (surface_res == NULL) { + fs_output_set_surface(fsout, NULL, 0, 0, 0); + return; + } + + surface = wl_resource_get_user_data(surface_res); + fs_output_set_surface(fsout, surface, 0, framerate, 1); + + fsout->pending.mode_feedback = + wl_resource_create(client, + &_wl_fullscreen_shell_mode_feedback_interface, + 1, feedback_id); + wl_resource_set_implementation(fsout->pending.mode_feedback, NULL, + fsout, mode_feedback_destroyed); + + wl_list_for_each(seat, &shell->compositor->seat_list, link) { + if (seat->keyboard && seat->keyboard->focus == NULL) + weston_surface_activate(surface, seat); + } +} + +struct _wl_fullscreen_shell_interface fullscreen_shell_implementation = { + fullscreen_shell_release, + fullscreen_shell_present_surface, + fullscreen_shell_present_surface_for_mode, +}; + +static void +output_created(struct wl_listener *listener, void *data) +{ + struct fullscreen_shell *shell; + + shell = container_of(listener, struct fullscreen_shell, + output_created_listener); + + fs_output_create(shell, data); +} + +static void +client_destroyed(struct wl_listener *listener, void *data) +{ + struct fullscreen_shell *shell = container_of(listener, + struct fullscreen_shell, + client_destroyed); + shell->client = NULL; +} + +static void +bind_fullscreen_shell(struct wl_client *client, void *data, uint32_t version, + uint32_t id) +{ + struct fullscreen_shell *shell = data; + struct wl_resource *resource; + + if (shell->client != NULL && shell->client != client) + return; + else if (shell->client == NULL) { + shell->client = client; + wl_client_add_destroy_listener(client, &shell->client_destroyed); + } + + resource = wl_resource_create(client, &_wl_fullscreen_shell_interface, + 1, id); + wl_resource_set_implementation(resource, + &fullscreen_shell_implementation, + shell, NULL); + + if (shell->compositor->capabilities & WESTON_CAP_CURSOR_PLANE) + _wl_fullscreen_shell_send_capability(resource, + _WL_FULLSCREEN_SHELL_CAPABILITY_CURSOR_PLANE); + + if (shell->compositor->capabilities & WESTON_CAP_ARBITRARY_MODES) + _wl_fullscreen_shell_send_capability(resource, + _WL_FULLSCREEN_SHELL_CAPABILITY_ARBITRARY_MODES); +} + +WL_EXPORT int +module_init(struct weston_compositor *compositor, + int *argc, char *argv[]) +{ + struct fullscreen_shell *shell; + struct weston_seat *seat; + struct weston_output *output; + + shell = malloc(sizeof *shell); + if (shell == NULL) + return -1; + + memset(shell, 0, sizeof *shell); + shell->compositor = compositor; + + shell->client_destroyed.notify = client_destroyed; + + weston_layer_init(&shell->layer, &compositor->cursor_layer.link); + + wl_list_init(&shell->output_list); + shell->output_created_listener.notify = output_created; + wl_signal_add(&compositor->output_created_signal, + &shell->output_created_listener); + wl_list_for_each(output, &compositor->output_list, link) + fs_output_create(shell, output); + + shell->seat_created_listener.notify = seat_created; + wl_signal_add(&compositor->seat_created_signal, + &shell->seat_created_listener); + wl_list_for_each(seat, &compositor->seat_list, link) + seat_created(NULL, seat); + + wl_global_create(compositor->wl_display, + &_wl_fullscreen_shell_interface, 1, shell, + bind_fullscreen_shell); + + return 0; +} |