diff --git a/include/wlr/xwayland/shell.h b/include/wlr/xwayland/shell.h new file mode 100644 index 000000000..50be0aaed --- /dev/null +++ b/include/wlr/xwayland/shell.h @@ -0,0 +1,57 @@ +/* + * 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_XWAYLAND_SHELL_H +#define WLR_XWAYLAND_SHELL_H + +#include +#include + +/** + * The Xwayland shell. + * + * This is a shell only exposed to Xwayland. + */ +struct wlr_xwayland_shell_v1 { + struct wl_global *global; + + struct { + struct wl_signal new_surface; // struct wlr_xwayland_surface_v1 + } events; + + // private state + + struct wl_listener display_destroy; +}; + +/** + * An Xwayland shell surface. + */ +struct wlr_xwayland_surface_v1 { + struct wlr_surface *surface; + uint64_t serial; + + // private state + + struct wl_resource *resource; + struct wlr_xwayland_shell_v1 *shell; + bool added; + + struct wl_listener surface_destroy; +}; + +/** + * Create the xwayland_shell_v1 global. + * + * Compositors should add a global filter (see wl_display_set_global_filter()) + * to only expose this global to Xwayland clients. + */ +struct wlr_xwayland_shell_v1 *wlr_xwayland_shell_v1_create( + struct wl_display *display, uint32_t version); + +#endif diff --git a/protocol/meson.build b/protocol/meson.build index 726b01fc8..1928df9fc 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -24,6 +24,7 @@ protocols = { 'ext-session-lock-v1': wl_protocol_dir / 'staging/ext-session-lock/ext-session-lock-v1.xml', 'single-pixel-buffer-v1': wl_protocol_dir / 'staging/single-pixel-buffer/single-pixel-buffer-v1.xml', 'xdg-activation-v1': wl_protocol_dir / 'staging/xdg-activation/xdg-activation-v1.xml', + 'xwayland-shell-v1': wl_protocol_dir / 'staging/xwayland-shell/xwayland-shell-v1.xml', # Unstable upstream protocols 'fullscreen-shell-unstable-v1': wl_protocol_dir / 'unstable/fullscreen-shell/fullscreen-shell-unstable-v1.xml', diff --git a/xwayland/meson.build b/xwayland/meson.build index 4d8ed5f45..0e36b61e1 100644 --- a/xwayland/meson.build +++ b/xwayland/meson.build @@ -68,6 +68,7 @@ wlr_files += files( 'selection/outgoing.c', 'selection/selection.c', 'server.c', + 'shell.c', 'sockets.c', 'xwayland.c', 'xwm.c', diff --git a/xwayland/shell.c b/xwayland/shell.c new file mode 100644 index 000000000..d3e2fd0a9 --- /dev/null +++ b/xwayland/shell.c @@ -0,0 +1,185 @@ +#include +#include +#include +#include + +#include "xwayland-shell-v1-protocol.h" + +#define SHELL_VERSION 1 + +static void destroy_resource(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct xwayland_shell_v1_interface shell_impl; +static const struct xwayland_surface_v1_interface xwl_surface_impl; + +/** + * Get a struct wlr_xwayland_shell_v1 from a resource. + */ +static struct wlr_xwayland_shell_v1 *shell_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &xwayland_shell_v1_interface, &shell_impl)); + return wl_resource_get_user_data(resource); +} + +/** + * Get a struct wlr_xwayland_surface_v1 from a resource. + * + * Returns NULL if the Xwayland surface is inert. + */ +static struct wlr_xwayland_surface_v1 *xwl_surface_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &xwayland_surface_v1_interface, &xwl_surface_impl)); + return wl_resource_get_user_data(resource); +} + +static void xwl_surface_role_commit(struct wlr_surface *surface) { + struct wlr_xwayland_surface_v1 *xwl_surface = surface->role_data; + + if (xwl_surface->serial != 0 && !xwl_surface->added) { + xwl_surface->added = true; + wl_signal_emit_mutable(&xwl_surface->shell->events.new_surface, + xwl_surface); + } +} + +static void xwl_surface_role_destroy(struct wlr_surface *surface) { + struct wlr_xwayland_surface_v1 *xwl_surface = surface->role_data; + wl_list_remove(&xwl_surface->surface_destroy.link); + wl_resource_set_user_data(xwl_surface->resource, NULL); // make inert + free(xwl_surface); +} + +static const struct wlr_surface_role xwl_surface_role = { + .name = "xwayland_surface_v1", + .commit = xwl_surface_role_commit, + .destroy = xwl_surface_role_destroy, +}; + +static void xwl_surface_handle_set_serial(struct wl_client *client, + struct wl_resource *resource, uint32_t serial_lo, uint32_t serial_hi) { + struct wlr_xwayland_surface_v1 *xwl_surface = + xwl_surface_from_resource(resource); + if (xwl_surface == NULL) { + return; + } + + if (xwl_surface->serial != 0) { + wl_resource_post_error(resource, + XWAYLAND_SURFACE_V1_ERROR_ALREADY_ASSOCIATED, + "xwayland_surface_v1 is already associated with another X11 serial"); + return; + } + + xwl_surface->serial = ((uint64_t)serial_hi << 32) | serial_lo; +} + +static const struct xwayland_surface_v1_interface xwl_surface_impl = { + .destroy = destroy_resource, + .set_serial = xwl_surface_handle_set_serial, +}; + +static void xwl_surface_handle_surface_destroy(struct wl_listener *listener, + void *data) { + struct wlr_xwayland_surface_v1 *xwl_surface = + wl_container_of(listener, xwl_surface, surface_destroy); + wlr_surface_destroy_role_object(xwl_surface->surface); +} + +static void xwl_surface_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_xwayland_surface_v1 *xwl_surface = + xwl_surface_from_resource(resource); + if (xwl_surface != NULL) { + wlr_surface_destroy_role_object(xwl_surface->surface); + } +} + +static void shell_handle_get_xwayland_surface(struct wl_client *client, + struct wl_resource *shell_resource, uint32_t id, + struct wl_resource *surface_resource) { + struct wlr_xwayland_shell_v1 *shell = shell_from_resource(shell_resource); + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + + struct wlr_xwayland_surface_v1 *xwl_surface = calloc(1, sizeof(*xwl_surface)); + if (xwl_surface == NULL) { + wl_client_post_no_memory(client); + return; + } + + if (!wlr_surface_set_role(surface, &xwl_surface_role, xwl_surface, + shell_resource, XWAYLAND_SHELL_V1_ERROR_ROLE)) { + free(xwl_surface); + return; + } + + xwl_surface->surface = surface; + xwl_surface->shell = shell; + + uint32_t version = wl_resource_get_version(shell_resource); + xwl_surface->resource = wl_resource_create(client, + &xwayland_surface_v1_interface, version, id); + if (xwl_surface->resource == NULL) { + free(xwl_surface); + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(xwl_surface->resource, &xwl_surface_impl, + xwl_surface, xwl_surface_handle_resource_destroy); + + xwl_surface->surface_destroy.notify = xwl_surface_handle_surface_destroy; + wl_signal_add(&surface->events.destroy, &xwl_surface->surface_destroy); +} + +static const struct xwayland_shell_v1_interface shell_impl = { + .destroy = destroy_resource, + .get_xwayland_surface = shell_handle_get_xwayland_surface, +}; + +static void shell_bind(struct wl_client *client, void *data, uint32_t version, + uint32_t id) { + struct wlr_xwayland_shell_v1 *shell = data; + + struct wl_resource *resource = wl_resource_create(client, + &xwayland_shell_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &shell_impl, shell, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_xwayland_shell_v1 *shell = + wl_container_of(listener, shell, display_destroy); + wl_list_remove(&shell->display_destroy.link); + wl_global_destroy(shell->global); + free(shell); +} + +struct wlr_xwayland_shell_v1 *wlr_xwayland_shell_v1_create( + struct wl_display *display, uint32_t version) { + assert(version <= SHELL_VERSION); + + struct wlr_xwayland_shell_v1 *shell = calloc(1, sizeof(*shell)); + if (shell == NULL) { + return NULL; + } + + shell->global = wl_global_create(display, &xwayland_shell_v1_interface, + version, shell, shell_bind); + if (shell->global == NULL) { + free(shell); + return NULL; + } + + wl_signal_init(&shell->events.new_surface); + + shell->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &shell->display_destroy); + + return shell; +}