weston/libweston/output-capture.c
Philipp Zabel efffd0d8a4 Revert "libweston: let weston_output_update_capture_info() take drm_format"
This reverts commit af5acbc9cb.

Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
2023-01-28 12:59:16 +00:00

697 lines
21 KiB
C

/*
* Copyright 2022 Collabora, Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "config.h"
#include <assert.h>
#include <libweston/libweston.h>
#include <libweston/weston-log.h>
#include "libweston-internal.h"
#include "output-capture.h"
#include "pixel-formats.h"
#include "shared/helpers.h"
#include "shared/weston-drm-fourcc.h"
#include "shared/xalloc.h"
#include "weston-output-capture-server-protocol.h"
/* Lifetimes
*
* weston_output_capture_info is created at weston_output enable, and
* destroyed at weston_output disable. It maintains lists of
* weston_capture_source and weston_capture_task.
*
* Protocol request weston_capture_v1.create creates weston_capture_source
* whose lifetime is equal to the weston_capture_source_v1 protocol object
* (wl_resource) lifetime.
*
* weston_capture_source is associated with a weston_output. When the
* weston_output is disabled, weston_capture_source is removed from the list
* in weston_output_capture_info and any pending task is retired as failed.
* Furhter capture attempts on the source will be immediately failed.
*
* Protocol request weston_capture_source_v1.capture creates
* weston_capture_task, if the weston_capture_source still has its output and
* no pending task. weston_capture_task becomes the pending task for the
* weston_capture_source, and is added to the list in
* weston_output_capture_info. Retiring weston_capture_task destroys it.
*
* Each weston_capture_task is associated with a wl_buffer (weston_buffer).
* If the buffer is destroyed, the task is retired as failed.
*
* Operation
*
* Each weston_capture_source has a "pixel source" property. Pixel source
* describes what the capture shall actually contain. See
* weston_capture_v1.create request in the protocol specification.
* One pixel source can be provided by at most one component at a time.
*
* Whenever a renderer or DRM-backend is repainting an output, they will
* use weston_output_pull_capture_task() at the appropriate stages to see
* if there are any capture tasks to be serviced for a specific pixel source.
* The renderer or DRM-backend must then retire the returned tasks by either
* failing or completing them.
*
* When an output repaint completes, no weston_capture_task shall remain in
* the list. Renderers or backends could stash them in their own lists though.
*
* In order to allow clients to allocate correctly sized and formatted buffers
* to hold captured images, weston_output_capture_info maintains the current
* size and format for each type of pixel source. Renderers and DRM-backend
* who provide pixel sources are also responsible for keeping the buffer
* requirements information up-to-date with
* weston_output_update_capture_info().
*/
/** Implementation of weston_capture_source_v1 protocol object */
struct weston_capture_source {
struct wl_resource *resource;
/* struct weston_output_capture_info::capture_source_list */
struct wl_list link;
enum weston_output_capture_source pixel_source;
/* weston_output_capture_info_destroy() will reset this. */
struct weston_output *output;
struct weston_capture_task *pending;
};
/** A pending task to capture an output */
struct weston_capture_task {
/* We get cleaned up through owner->pending pointing to us. */
struct weston_capture_source *owner;
/* struct weston_output_capture_info::pending_capture_list */
struct wl_list link;
struct weston_buffer *buffer;
struct wl_listener buffer_resource_destroy_listener;
};
/** Buffer requirements broadcasting for a pixel source */
struct weston_output_capture_source_info {
enum weston_output_capture_source pixel_source;
int width;
int height;
uint32_t drm_format;
};
/** Capture records for an output */
struct weston_output_capture_info {
/* struct weston_capture_task::link */
struct wl_list pending_capture_list;
/* struct weston_capture_source::link */
struct wl_list capture_source_list;
struct weston_output_capture_source_info source_info[WESTON_OUTPUT_CAPTURE_SOURCE__COUNT];
};
/** Create capture tracking information on weston_output enable */
struct weston_output_capture_info *
weston_output_capture_info_create(void)
{
struct weston_output_capture_info *ci;
unsigned i;
ci = xzalloc(sizeof *ci);
wl_list_init(&ci->pending_capture_list);
wl_list_init(&ci->capture_source_list);
/*
* Initialize to no sources available by leaving
* width, height and drm_format as zero.
*/
for (i = 0; i < ARRAY_LENGTH(ci->source_info); i++)
ci->source_info[i].pixel_source = i;
return ci;
}
/** Clean up capture tracking information on weston_output disable */
void
weston_output_capture_info_destroy(struct weston_output_capture_info **cip)
{
struct weston_output_capture_info *ci = *cip;
struct weston_capture_source *csrc, *tmp;
assert(ci);
/* Unlink sources. They get destroyed by their wl_resource later. */
wl_list_for_each_safe(csrc, tmp, &ci->capture_source_list, link) {
csrc->output = NULL;
wl_list_remove(&csrc->link);
wl_list_init(&csrc->link);
if (csrc->pending)
weston_capture_task_retire_failed(csrc->pending, "output removed");
}
assert(wl_list_empty(&ci->pending_capture_list));
free(ci);
*cip = NULL;
}
/** Assert that all capture tasks were taken
*
* This is called at the end of a weston_output repaint cycle when the renderer
* and the backend have had their chance to service all pending capture tasks.
* The remaining tasks would not be serviced by anything, so make sure none
* linger.
*/
void
weston_output_capture_info_repaint_done(struct weston_output_capture_info *ci)
{
assert(wl_list_empty(&ci->pending_capture_list));
}
static bool
source_info_is_available(const struct weston_output_capture_source_info *csi)
{
return csi->width > 0 && csi->height > 0 &&
csi->drm_format != DRM_FORMAT_INVALID;
}
static void
capture_info_send_source_info(struct weston_output_capture_info *ci,
struct weston_output_capture_source_info *csi)
{
struct weston_capture_source *csrc;
wl_list_for_each(csrc, &ci->capture_source_list, link) {
if (csrc->pixel_source != csi->pixel_source)
continue;
weston_capture_source_v1_send_format(csrc->resource,
csi->drm_format);
weston_capture_source_v1_send_size(csrc->resource,
csi->width, csi->height);
}
}
static struct weston_output_capture_source_info *
capture_info_get_csi(struct weston_output_capture_info *ci,
enum weston_output_capture_source src)
{
int srcidx = src;
assert(ci);
assert(srcidx >= 0 && srcidx < (int)ARRAY_LENGTH(ci->source_info));
return &ci->source_info[srcidx];
}
/** Update capture requirements broadcast to clients
*
* This is called by renderers and DRM-backend to update the buffer
* requirements information that is delivered to clients wanting to capture
* the output. This is how clients know what size and format buffer they
* need to allocate for the given output and pixel source.
*
* \param output The output whose capture info to update.
* \param src The source type on the output.
* \param width The new buffer width.
* \param height The new buffer height.
* \param format The new pixel format.
*
* If any one of width, height or format is zero/NULL, the source becomes
* unavailable to clients. Otherwise the source becomes available.
*
* Initially all sources are unavailable.
*/
WL_EXPORT void
weston_output_update_capture_info(struct weston_output *output,
enum weston_output_capture_source src,
int width, int height,
const struct pixel_format_info *format)
{
struct weston_output_capture_info *ci = output->capture_info;
struct weston_output_capture_source_info *csi;
csi = capture_info_get_csi(ci, src);
if (csi->width == width &&
csi->height == height &&
csi->drm_format == format->format)
return;
csi->width = width;
csi->height = height;
csi->drm_format = format->format;
if (source_info_is_available(csi)) {
capture_info_send_source_info(ci, csi);
} else {
struct weston_capture_task *ct, *tmp;
/*
* This source just became unavailable, so fail all pending
* tasks using it.
*/
wl_list_for_each_safe(ct, tmp,
&ci->pending_capture_list, link) {
if (ct->owner->pixel_source != csi->pixel_source)
continue;
weston_capture_task_retire_failed(ct, "source removed");
}
}
}
static bool
buffer_is_compatible(struct weston_buffer *buffer,
struct weston_output_capture_source_info *csi)
{
return buffer->width == csi->width &&
buffer->height == csi->height &&
buffer->pixel_format->format == csi->drm_format &&
buffer->format_modifier == DRM_FORMAT_MOD_LINEAR;
}
static void
weston_capture_task_destroy(struct weston_capture_task *ct)
{
if (ct->owner->pixel_source != WESTON_OUTPUT_CAPTURE_SOURCE_WRITEBACK &&
ct->owner->output)
weston_output_disable_planes_decr(ct->owner->output);
assert(ct->owner->pending == ct);
ct->owner->pending = NULL;
wl_list_remove(&ct->link);
wl_list_remove(&ct->buffer_resource_destroy_listener.link);
free(ct);
}
static void
weston_capture_task_buffer_destroy_handler(struct wl_listener *l, void *data)
{
struct weston_capture_task *ct =
wl_container_of(l, ct, buffer_resource_destroy_listener);
/*
* Client destroyed the wl_buffer object. By protocol spec, this is
* undefined behaviour. Do the most sensible thing.
*/
weston_capture_task_retire_failed(ct, "wl_buffer destroyed");
}
static struct weston_capture_task *
weston_capture_task_create(struct weston_capture_source *csrc,
struct weston_buffer *buffer)
{
struct weston_capture_task *ct;
ct = xzalloc(sizeof *ct);
ct->owner = csrc;
/* Owner will explicitly destroy us if the owner gets destroyed. */
ct->buffer = buffer;
ct->buffer_resource_destroy_listener.notify = weston_capture_task_buffer_destroy_handler;
wl_resource_add_destroy_listener(buffer->resource,
&ct->buffer_resource_destroy_listener);
wl_list_insert(&csrc->output->capture_info->pending_capture_list,
&ct->link);
if (ct->owner->pixel_source != WESTON_OUTPUT_CAPTURE_SOURCE_WRITEBACK)
weston_output_disable_planes_incr(ct->owner->output);
return ct;
}
static bool
capture_is_authorized(struct weston_capture_source *csrc)
{
struct weston_compositor *compositor = csrc->output->compositor;
const struct weston_output_capture_client who = {
.client = wl_resource_get_client(csrc->resource),
.output = csrc->output,
};
struct weston_output_capture_attempt att = {
.who = &who,
.authorized = false,
.denied = false,
};
wl_signal_emit(&compositor->output_capture.ask_auth, &att);
return att.authorized && !att.denied;
}
/** Fetch the next capture task
*
* This is used by renderers and DRM-backend to get the next capture task
* they want to service. Only tasks for the given pixel source will be
* returned.
*
* Width, height and drm_format are for ensuring that
* weston_output_update_capture_info() was up-to-date before this.
*
* \return A capture task, or NULL if no more tasks.
*/
WL_EXPORT struct weston_capture_task *
weston_output_pull_capture_task(struct weston_output *output,
enum weston_output_capture_source src,
int width, int height,
const struct pixel_format_info *format)
{
struct weston_output_capture_info *ci = output->capture_info;
struct weston_output_capture_source_info *csi;
struct weston_capture_task *ct, *tmp;
/*
* Make sure the capture provider (renderers, DRM-backend) called
* weston_output_update_capture_info() if something changed, so that
* the 'retry' event keeps its promise of size/format events been
* already sent.
*/
csi = capture_info_get_csi(ci, src);
assert(csi->width == width);
assert(csi->height == height);
assert(csi->drm_format == format->format);
wl_list_for_each_safe(ct, tmp, &ci->pending_capture_list, link) {
assert(ct->owner->output == output);
if (ct->owner->pixel_source != src)
continue;
if (!capture_is_authorized(ct->owner)) {
weston_capture_task_retire_failed(ct, "unauthorized");
continue;
}
/*
* Tell the client to retry, if requirements changed after
* the task was filed.
*/
if (!buffer_is_compatible(ct->buffer, csi)) {
weston_capture_source_v1_send_retry(ct->owner->resource);
weston_capture_task_destroy(ct);
continue;
}
/* pass ct ownership to the caller */
wl_list_remove(&ct->link);
wl_list_init(&ct->link);
return ct;
}
return NULL;
}
/** Check if any capture tasks are waiting on the output */
WL_EXPORT bool
weston_output_has_capture_tasks(struct weston_output *output)
{
struct weston_output_capture_info *ci = output->capture_info;
return !wl_list_empty(&ci->pending_capture_list);
}
/** Get the destination buffer */
WL_EXPORT struct weston_buffer *
weston_capture_task_get_buffer(struct weston_capture_task *ct)
{
return ct->buffer;
}
/** Signal completion of the capture task
*
* Sends 'complete' protocol event to the client, and destroys the task.
*/
WL_EXPORT void
weston_capture_task_retire_complete(struct weston_capture_task *ct)
{
weston_capture_source_v1_send_complete(ct->owner->resource);
weston_capture_task_destroy(ct);
}
/** Signal failure of the capture task
*
* Sends 'failed' protocol event to the client, and destroys the task.
*/
WL_EXPORT void
weston_capture_task_retire_failed(struct weston_capture_task *ct,
const char *err_msg)
{
weston_capture_source_v1_send_failed(ct->owner->resource, err_msg);
weston_capture_task_destroy(ct);
}
static void
destroy_capture_source(struct wl_resource *csrc_resource)
{
struct weston_capture_source *csrc;
csrc = wl_resource_get_user_data(csrc_resource);
assert(csrc_resource == csrc->resource);
if (csrc->pending)
weston_capture_task_destroy(csrc->pending);
wl_list_remove(&csrc->link);
free(csrc);
}
static void
weston_capture_source_v1_destroy(struct wl_client *client,
struct wl_resource *csrc_resource)
{
wl_resource_destroy(csrc_resource);
}
static void
weston_capture_source_v1_capture(struct wl_client *client,
struct wl_resource *csrc_resource,
struct wl_resource *buffer_resource)
{
struct weston_output_capture_source_info *csi;
struct weston_capture_source *csrc;
struct weston_buffer *buffer;
csrc = wl_resource_get_user_data(csrc_resource);
assert(csrc_resource == csrc->resource);
/* A capture task already exists? */
if (csrc->pending) {
wl_resource_post_error(csrc->resource,
WESTON_CAPTURE_SOURCE_V1_ERROR_SEQUENCE,
"capture attempted before previous capture retired");
return;
}
/* weston_output disabled after creating the source? */
if (!csrc->output) {
weston_capture_source_v1_send_failed(csrc->resource, "output removed");
return;
}
/* Is the pixel source not available? */
csi = capture_info_get_csi(csrc->output->capture_info,
csrc->pixel_source);
if (!source_info_is_available(csi)) {
weston_capture_source_v1_send_failed(csrc->resource, "source unavailable");
return;
}
buffer = weston_buffer_from_resource(csrc->output->compositor,
buffer_resource);
if (!buffer) {
wl_client_post_no_memory(client);
return;
}
/* If the buffer not up-to-date with the size and format? */
if (!buffer_is_compatible(buffer, csi)) {
weston_capture_source_v1_send_retry(csrc->resource);
return;
}
csrc->pending = weston_capture_task_create(csrc, buffer);
weston_output_schedule_repaint(csrc->output);
}
static const struct weston_capture_source_v1_interface weston_capture_source_v1_impl = {
.destroy = weston_capture_source_v1_destroy,
.capture = weston_capture_source_v1_capture,
};
static int32_t
pixel_source_from_proto(uint32_t v)
{
switch (v) {
case WESTON_CAPTURE_V1_SOURCE_WRITEBACK:
return WESTON_OUTPUT_CAPTURE_SOURCE_WRITEBACK;
case WESTON_CAPTURE_V1_SOURCE_FRAMEBUFFER:
return WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER;
case WESTON_CAPTURE_V1_SOURCE_FULL_FRAMEBUFFER:
return WESTON_OUTPUT_CAPTURE_SOURCE_FULL_FRAMEBUFFER;
case WESTON_CAPTURE_V1_SOURCE_BLENDING:
return WESTON_OUTPUT_CAPTURE_SOURCE_BLENDING;
default:
return -1;
}
}
static void
weston_capture_v1_create(struct wl_client *client,
struct wl_resource *capture_resource,
struct wl_resource *output_resource,
uint32_t source,
uint32_t capture_source_new_id)
{
int32_t isrc = pixel_source_from_proto(source);
struct weston_capture_source *csrc;
struct weston_head *head;
if (isrc < 0) {
wl_resource_post_error(capture_resource,
WESTON_CAPTURE_V1_ERROR_INVALID_SOURCE,
"%u is not a valid source", source);
return;
}
csrc = zalloc(sizeof *csrc);
if (!csrc) {
wl_client_post_no_memory(client);
return;
}
csrc->pixel_source = isrc;
wl_list_init(&csrc->link);
csrc->resource = wl_resource_create(client,
&weston_capture_source_v1_interface,
wl_resource_get_version(capture_resource),
capture_source_new_id);
if (!csrc->resource) {
free(csrc);
wl_client_post_no_memory(client);
return;
}
wl_resource_set_implementation(csrc->resource,
&weston_capture_source_v1_impl,
csrc, destroy_capture_source);
head = weston_head_from_resource(output_resource);
if (head) {
struct weston_output *output = head->output;
struct weston_output_capture_info *ci = output->capture_info;
struct weston_output_capture_source_info *csi;
csi = capture_info_get_csi(ci, csrc->pixel_source);
wl_list_insert(&ci->capture_source_list, &csrc->link);
csrc->output = output;
if (source_info_is_available(csi))
capture_info_send_source_info(ci, csi);
}
/*
* if (!head) then weston_capture_source_v1_capture() will respond with
* the failed event.
*/
}
static void
weston_capture_v1_destroy(struct wl_client *client,
struct wl_resource *resource)
{
wl_resource_destroy(resource);
}
static const struct weston_capture_v1_interface weston_capture_v1_impl = {
.destroy = weston_capture_v1_destroy,
.create = weston_capture_v1_create,
};
static void
bind_weston_capture(struct wl_client *client,
void *data, uint32_t version, uint32_t id)
{
struct wl_resource *resource;
/* Access control is done at capture request. */
resource = wl_resource_create(client, &weston_capture_v1_interface,
version, id);
if (!resource) {
wl_client_post_no_memory(client);
return;
}
wl_resource_set_implementation(resource, &weston_capture_v1_impl,
NULL, NULL);
}
void
weston_compositor_install_capture_protocol(struct weston_compositor *compositor)
{
compositor->output_capture.weston_capture_v1 =
wl_global_create(compositor->wl_display,
&weston_capture_v1_interface,
1, NULL, bind_weston_capture);
abort_oom_if_null(compositor->output_capture.weston_capture_v1);
}
/** Add a new authority that may authorize or deny screenshots
*
* \param compositor The compositor instance.
* \param listener The listener to populate, and which will be passed as the
* listener to the auth callback.
* \param auth The callback function which shall be called every time any
* client sends a request to capture an output.
*
* The callback function \c auth is called with argument \c att. If you
* want to authorize the screenshot after inspecting the fields in
* \c att->who , you must set \c att->authorized to true. If you want to
* deny the screenshot instead, set \c att->denied to true. Otherwise,
* do not change anything.
*
* Any screenshot is carried out only if after iterating through all
* authorities \c att->authorized is true and \c att->denied is false.
* Both default to false, which forbids screenshots without any authorities.
*
* You can remove an added authority by \c wl_list_remove(&listener->link) .
*/
WL_EXPORT void
weston_compositor_add_screenshot_authority(struct weston_compositor *compositor,
struct wl_listener *listener,
void (*auth)(struct wl_listener *l,
struct weston_output_capture_attempt *att))
{
listener->notify = (wl_notify_func_t)auth;
wl_signal_add(&compositor->output_capture.ask_auth, listener);
}