diff --git a/include/wlr/types/wlr_viewporter.h b/include/wlr/types/wlr_viewporter.h new file mode 100644 index 00000000..b695420d --- /dev/null +++ b/include/wlr/types/wlr_viewporter.h @@ -0,0 +1,34 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_VIEWPORTER_H +#define WLR_TYPES_WLR_VIEWPORTER_H + +#include + +struct wlr_viewporter { + struct wl_global *global; + + struct { + struct wl_signal destroy; + } events; + + struct wl_listener display_destroy; +}; + +struct wlr_viewport { + struct wl_resource *resource; + struct wlr_surface *surface; + + struct wl_listener surface_destroy; + struct wl_listener surface_commit; +}; + +struct wlr_viewporter *wlr_viewporter_create(struct wl_display *display); + +#endif diff --git a/protocol/meson.build b/protocol/meson.build index 1b90b4f0..2369b70e 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -14,6 +14,7 @@ protocols = { # Stable upstream protocols 'xdg-shell': wl_protocol_dir / 'stable/xdg-shell/xdg-shell.xml', 'presentation-time': wl_protocol_dir / 'stable/presentation-time/presentation-time.xml', + 'viewporter': wl_protocol_dir / 'stable/viewporter/viewporter.xml', # Unstable upstream protocols 'fullscreen-shell-unstable-v1': wl_protocol_dir / 'unstable/fullscreen-shell/fullscreen-shell-unstable-v1.xml', 'idle-inhibit-unstable-v1': wl_protocol_dir / 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml', diff --git a/types/meson.build b/types/meson.build index face4bdf..998e6b45 100644 --- a/types/meson.build +++ b/types/meson.build @@ -64,6 +64,7 @@ wlr_files += files( 'wlr_tablet_tool.c', 'wlr_text_input_v3.c', 'wlr_touch.c', + 'wlr_viewporter.c', 'wlr_virtual_keyboard_v1.c', 'wlr_virtual_pointer_v1.c', 'wlr_xcursor_manager.c', diff --git a/types/wlr_viewporter.c b/types/wlr_viewporter.c new file mode 100644 index 00000000..9b7e1099 --- /dev/null +++ b/types/wlr_viewporter.c @@ -0,0 +1,224 @@ +#include +#include +#include +#include +#include +#include "util/signal.h" +#include "viewporter-protocol.h" + +#define VIEWPORTER_VERSION 1 + +static const struct wp_viewport_interface viewport_impl; + +// Returns NULL if the viewport is inert +static struct wlr_viewport *viewport_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wp_viewport_interface, + &viewport_impl)); + return wl_resource_get_user_data(resource); +} + +static void viewport_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void viewport_handle_set_source(struct wl_client *client, + struct wl_resource *resource, wl_fixed_t x_fixed, wl_fixed_t y_fixed, + wl_fixed_t width_fixed, wl_fixed_t height_fixed) { + struct wlr_viewport *viewport = viewport_from_resource(resource); + if (viewport == NULL) { + wl_resource_post_error(resource, WP_VIEWPORT_ERROR_NO_SURFACE, + "wp_viewport.set_source sent after wl_surface has been destroyed"); + return; + } + + struct wlr_surface_state *pending = &viewport->surface->pending; + + double x = wl_fixed_to_double(x_fixed); + double y = wl_fixed_to_double(y_fixed); + double width = wl_fixed_to_double(width_fixed); + double height = wl_fixed_to_double(height_fixed); + + if (x == -1.0 && y == -1.0 && width == -1.0 && height == -1.0) { + pending->viewport.has_src = false; + } else if (x < 0 || y < 0 || width <= 0 || height <= 0) { + wl_resource_post_error(resource, WP_VIEWPORT_ERROR_BAD_VALUE, + "wl_viewport.set_source sent with invalid values"); + return; + } else { + pending->viewport.has_src = true; + } + + pending->viewport.src.x = x; + pending->viewport.src.y = y; + pending->viewport.src.width = width; + pending->viewport.src.height = height; + + pending->committed |= WLR_SURFACE_STATE_VIEWPORT; +} + +static void viewport_handle_set_destination(struct wl_client *client, + struct wl_resource *resource, int32_t width, int32_t height) { + struct wlr_viewport *viewport = viewport_from_resource(resource); + if (viewport == NULL) { + wl_resource_post_error(resource, WP_VIEWPORT_ERROR_NO_SURFACE, + "wp_viewport.set_destination sent after wl_surface has been destroyed"); + return; + } + + struct wlr_surface_state *pending = &viewport->surface->pending; + + if (width == -1 && height == -1) { + pending->viewport.has_dst = false; + } else if (width <= 0 || height <= 0) { + wl_resource_post_error(resource, WP_VIEWPORT_ERROR_BAD_VALUE, + "wl_viewport.set_destination sent with invalid values"); + return; + } else { + pending->viewport.has_dst = true; + } + + pending->viewport.dst_width = width; + pending->viewport.dst_height = height; + + pending->committed |= WLR_SURFACE_STATE_VIEWPORT; +} + +static const struct wp_viewport_interface viewport_impl = { + .destroy = viewport_handle_destroy, + .set_source = viewport_handle_set_source, + .set_destination = viewport_handle_set_destination, +}; + +static void viewport_destroy(struct wlr_viewport *viewport) { + if (viewport == NULL) { + return; + } + wl_resource_set_user_data(viewport->resource, NULL); + wl_list_remove(&viewport->surface_destroy.link); + wl_list_remove(&viewport->surface_commit.link); + free(viewport); +} + +static void viewport_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_viewport *viewport = viewport_from_resource(resource); + viewport_destroy(viewport); +} + +static void viewport_handle_surface_destroy(struct wl_listener *listener, + void *data) { + struct wlr_viewport *viewport = + wl_container_of(listener, viewport, surface_destroy); + viewport_destroy(viewport); +} + +static void viewport_handle_surface_commit(struct wl_listener *listener, + void *data) { + struct wlr_viewport *viewport = + wl_container_of(listener, viewport, surface_commit); + + struct wlr_surface_state *current = &viewport->surface->pending; + + if (!current->viewport.has_dst && + (floor(current->viewport.src.width) != current->viewport.src.width || + floor(current->viewport.src.height) != current->viewport.src.height)) { + wl_resource_post_error(viewport->resource, WP_VIEWPORT_ERROR_BAD_SIZE, + "wl_viewport.set_source width and height must be integers " + "when the destination rectangle is unset"); + return; + } + + if (current->viewport.has_src && current->buffer_resource != NULL && + (current->viewport.src.x + current->viewport.src.width > + current->buffer_width || + current->viewport.src.y + current->viewport.src.height > + current->buffer_height)) { + wl_resource_post_error(viewport->resource, WP_VIEWPORT_ERROR_OUT_OF_BUFFER, + "source rectangle out of buffer bounds"); + return; + } +} + +static void viewporter_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void viewporter_handle_get_viewport(struct wl_client *client, + struct wl_resource *resource, uint32_t id, + struct wl_resource *surface_resource) { + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + + struct wlr_viewport *viewport = calloc(1, sizeof(*viewport)); + if (viewport == NULL) { + wl_client_post_no_memory(client); + return; + } + + uint32_t version = wl_resource_get_version(resource); + viewport->resource = wl_resource_create(client, &wp_viewport_interface, + version, id); + if (viewport->resource == NULL) { + wl_client_post_no_memory(client); + free(viewport); + return; + } + wl_resource_set_implementation(viewport->resource, &viewport_impl, + viewport, viewport_handle_resource_destroy); + + viewport->surface = surface; + + viewport->surface_destroy.notify = viewport_handle_surface_destroy; + wl_signal_add(&surface->events.destroy, &viewport->surface_destroy); + + viewport->surface_commit.notify = viewport_handle_surface_commit; + wl_signal_add(&surface->events.commit, &viewport->surface_commit); +} + +static const struct wp_viewporter_interface viewporter_impl = { + .destroy = viewporter_handle_destroy, + .get_viewport = viewporter_handle_get_viewport, +}; + +static void viewporter_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_viewporter *viewporter = data; + + struct wl_resource *resource = wl_resource_create(client, + &wp_viewporter_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &viewporter_impl, viewporter, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_viewporter *viewporter = + wl_container_of(listener, viewporter, display_destroy); + wlr_signal_emit_safe(&viewporter->events.destroy, NULL); + wl_global_destroy(viewporter->global); + free(viewporter); +} + +struct wlr_viewporter *wlr_viewporter_create(struct wl_display *display) { + struct wlr_viewporter *viewporter = calloc(1, sizeof(*viewporter)); + if (viewporter == NULL) { + return NULL; + } + + viewporter->global = wl_global_create(display, &wp_viewporter_interface, + VIEWPORTER_VERSION, viewporter, viewporter_bind); + if (viewporter->global == NULL) { + free(viewporter); + return NULL; + } + + wl_signal_init(&viewporter->events.destroy); + + viewporter->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &viewporter->display_destroy); + + return viewporter; +}