diff --git a/include/wlr/types/wlr_content_type_v1.h b/include/wlr/types/wlr_content_type_v1.h new file mode 100644 index 00000000..3de39367 --- /dev/null +++ b/include/wlr/types/wlr_content_type_v1.h @@ -0,0 +1,36 @@ +/* + * 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_CONTENT_TYPE_V1_H +#define WLR_TYPES_WLR_CONTENT_TYPE_V1_H + +#include +#include "content-type-v1-protocol.h" + +struct wlr_surface; + +struct wlr_content_type_manager_v1 { + struct wl_global *global; + + struct { + struct wl_signal destroy; + } events; + + void *data; + + // private state + + struct wl_listener display_destroy; +}; + +struct wlr_content_type_manager_v1 *wlr_content_type_manager_v1_create( + struct wl_display *display); +enum wp_content_type_v1_type wlr_surface_get_content_type_v1( + struct wlr_content_type_manager_v1 *manager, struct wlr_surface *surface); + +#endif diff --git a/protocol/meson.build b/protocol/meson.build index 4d56bbf4..6ba4bb6e 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -18,6 +18,7 @@ protocols = { 'xdg-shell': wl_protocol_dir / 'stable/xdg-shell/xdg-shell.xml', # Staging upstream protocols + 'content-type-v1': wl_protocol_dir / 'staging/content-type/content-type-v1.xml', 'drm-lease-v1': wl_protocol_dir / 'staging/drm-lease/drm-lease-v1.xml', 'ext-idle-notify-v1': wl_protocol_dir / 'staging/ext-idle-notify/ext-idle-notify-v1.xml', 'ext-session-lock-v1': wl_protocol_dir / 'staging/ext-session-lock/ext-session-lock-v1.xml', diff --git a/types/meson.build b/types/meson.build index e558f5d3..400927da 100644 --- a/types/meson.build +++ b/types/meson.build @@ -35,6 +35,7 @@ wlr_files += files( 'buffer/resource.c', 'buffer/shm_client.c', 'wlr_compositor.c', + 'wlr_content_type_v1.c', 'wlr_cursor.c', 'wlr_damage_ring.c', 'wlr_data_control_v1.c', diff --git a/types/wlr_content_type_v1.c b/types/wlr_content_type_v1.c new file mode 100644 index 00000000..037228be --- /dev/null +++ b/types/wlr_content_type_v1.c @@ -0,0 +1,193 @@ +#include +#include +#include +#include + +#define CONTENT_TYPE_VERSION 1 + +struct wlr_content_type_v1_surface { + struct wl_resource *resource; + struct wlr_addon addon; + struct wl_listener commit; + enum wp_content_type_v1_type pending, current; +}; + +static void resource_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wp_content_type_v1_interface content_type_surface_impl; +static const struct wp_content_type_manager_v1_interface manager_impl; + +// Returns NULL if the resource is inert +static struct wlr_content_type_v1_surface *content_type_surface_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &wp_content_type_v1_interface, &content_type_surface_impl)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_content_type_manager_v1 *manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &wp_content_type_manager_v1_interface, &manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void content_type_surface_handle_set_content_type(struct wl_client *client, + struct wl_resource *resource, uint32_t type) { + struct wlr_content_type_v1_surface *content_type_surface = + content_type_surface_from_resource(resource); + if (content_type_surface == NULL) { + return; + } + content_type_surface->pending = type; +} + +static const struct wp_content_type_v1_interface content_type_surface_impl = { + .destroy = resource_handle_destroy, + .set_content_type = content_type_surface_handle_set_content_type, +}; + +static void content_type_surface_destroy( + struct wlr_content_type_v1_surface *content_type_surface) { + if (content_type_surface == NULL) { + return; + } + wlr_addon_finish(&content_type_surface->addon); + wl_list_remove(&content_type_surface->commit.link); + wl_resource_set_user_data(content_type_surface->resource, NULL); + free(content_type_surface); +} + +static void surface_addon_destroy(struct wlr_addon *addon) { + struct wlr_content_type_v1_surface *content_type_surface = + wl_container_of(addon, content_type_surface, addon); + content_type_surface_destroy(content_type_surface); +} + +static const struct wlr_addon_interface surface_addon_impl = { + .name = "wp_content_type_v1", + .destroy = surface_addon_destroy, +}; + +static void content_type_surface_handle_commit(struct wl_listener *listener, + void *data) { + struct wlr_content_type_v1_surface *content_type_surface = + wl_container_of(listener, content_type_surface, commit); + content_type_surface->current = content_type_surface->pending; +} + +static void content_type_surface_handle_resource_destroy( + struct wl_resource *resource) { + struct wlr_content_type_v1_surface *content_type_surface = + content_type_surface_from_resource(resource); + content_type_surface_destroy(content_type_surface); +} + +static void manager_handle_get_surface_content_type(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, + struct wl_resource *surface_resource) { + struct wlr_content_type_manager_v1 *manager = + manager_from_resource(manager_resource); + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + + if (wlr_addon_find(&surface->addons, manager, &surface_addon_impl) != NULL) { + wl_resource_post_error(manager_resource, + WP_CONTENT_TYPE_MANAGER_V1_ERROR_ALREADY_CONSTRUCTED, + "wp_content_type_v1 already constructed for this surface"); + return; + } + + struct wlr_content_type_v1_surface *content_type_surface = + calloc(1, sizeof(*content_type_surface)); + if (content_type_surface == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + + uint32_t version = wl_resource_get_version(manager_resource); + content_type_surface->resource = wl_resource_create(client, + &wp_content_type_v1_interface, version, id); + if (content_type_surface->resource == NULL) { + free(content_type_surface); + wl_resource_post_no_memory(manager_resource); + return; + } + wl_resource_set_implementation(content_type_surface->resource, + &content_type_surface_impl, content_type_surface, + content_type_surface_handle_resource_destroy); + + wlr_addon_init(&content_type_surface->addon, &surface->addons, + manager, &surface_addon_impl); + + content_type_surface->commit.notify = content_type_surface_handle_commit; + wl_signal_add(&surface->events.destroy, &content_type_surface->commit); +} + +static const struct wp_content_type_manager_v1_interface manager_impl = { + .destroy = resource_handle_destroy, + .get_surface_content_type = manager_handle_get_surface_content_type, +}; + +static void manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_content_type_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &wp_content_type_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &manager_impl, manager, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_content_type_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + + wl_signal_emit_mutable(&manager->events.destroy, NULL); + assert(wl_list_empty(&manager->events.destroy.listener_list)); + + wl_global_destroy(manager->global); + wl_list_remove(&manager->display_destroy.link); + free(manager); +} + +struct wlr_content_type_manager_v1 *wlr_content_type_manager_v1_create( + struct wl_display *display) { + struct wlr_content_type_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + + manager->global = wl_global_create(display, + &wp_content_type_manager_v1_interface, CONTENT_TYPE_VERSION, + manager, manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + wl_signal_init(&manager->events.destroy); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} + +enum wp_content_type_v1_type wlr_surface_get_content_type_v1( + struct wlr_content_type_manager_v1 *manager, struct wlr_surface *surface) { + struct wlr_addon *addon = + wlr_addon_find(&surface->addons, manager, &surface_addon_impl); + if (addon == NULL) { + return WP_CONTENT_TYPE_V1_TYPE_NONE; + } + + struct wlr_content_type_v1_surface *content_type_surface = + wl_container_of(addon, content_type_surface, addon); + return content_type_surface->current; +}