diff --git a/include/pipewire_screencast.h b/include/pipewire_screencast.h
index 64ae1412..f8cd88e9 100644
--- a/include/pipewire_screencast.h
+++ b/include/pipewire_screencast.h
@@ -7,7 +7,6 @@
#define XDPW_PWR_BUFFERS_MIN 2
#define XDPW_PWR_ALIGN 16
-void xdpw_pwr_dequeue_buffer(struct xdpw_screencast_instance *cast);
void xdpw_pwr_enqueue_buffer(struct xdpw_screencast_instance *cast);
void pwr_update_stream_param(struct xdpw_screencast_instance *cast);
void xdpw_pwr_stream_create(struct xdpw_screencast_instance *cast);
diff --git a/include/screencast_common.h b/include/screencast_common.h
index 7d327a2c..4a47cb1f 100644
--- a/include/screencast_common.h
+++ b/include/screencast_common.h
@@ -44,12 +44,17 @@ enum xdpw_frame_state {
XDPW_FRAME_STATE_SUCCESS,
};
+enum ext_screencopy_input_type {
+ EXT_SCREENCOPY_INPUT_TYPE_POINTER = 0,
+ EXT_SCREENCOPY_INPUT_TYPE_TABLET = 1,
+};
+
struct xdpw_output_chooser {
enum xdpw_chooser_types type;
char *cmd;
};
-struct xdpw_frame_damage {
+struct xdpw_damage {
uint32_t x;
uint32_t y;
uint32_t width;
@@ -60,11 +65,25 @@ struct xdpw_frame {
bool y_invert;
uint64_t tv_sec;
uint32_t tv_nsec;
- struct xdpw_frame_damage damage;
+ struct xdpw_damage damage;
struct xdpw_buffer *xdpw_buffer;
struct pw_buffer *pw_buffer;
};
+struct xdpw_cursor {
+ char *seat_name;
+ enum ext_screencopy_input_type input_type;
+ bool present;
+ bool damaged;
+ int32_t width;
+ int32_t height;
+ int32_t position_x;
+ int32_t position_y;
+ int32_t hotspot_x;
+ int32_t hotspot_y;
+ struct xdpw_buffer *xdpw_buffer;
+};
+
struct xdpw_screencopy_frame_info {
uint32_t width;
uint32_t height;
@@ -73,6 +92,13 @@ struct xdpw_screencopy_frame_info {
uint32_t format;
};
+struct xdpw_screencopy_cursor_frame_info {
+ char *seat_name;
+ enum ext_screencopy_input_type input_type;
+
+ struct xdpw_screencopy_frame_info frame_info[2];
+};
+
struct xdpw_buffer {
struct wl_list link;
enum buffer_type buffer_type;
@@ -90,6 +116,7 @@ struct xdpw_buffer {
struct gbm_bo *bo;
struct wl_buffer *buffer;
+ struct xdpw_damage damage;
};
struct xdpw_format_modifier_pair {
@@ -116,6 +143,7 @@ struct xdpw_screencast_context {
struct wl_list output_list;
struct wl_registry *registry;
struct zwlr_screencopy_manager_v1 *screencopy_manager;
+ struct ext_screencopy_manager_v1 *ext_screencopy_manager;
struct zxdg_output_manager_v1 *xdg_output_manager;
struct wl_shm *shm;
struct zwp_linux_dmabuf_v1 *linux_dmabuf;
@@ -141,6 +169,7 @@ struct xdpw_screencast_instance {
struct xdpw_frame current_frame;
enum xdpw_frame_state frame_state;
struct wl_list buffer_list;
+ struct wl_list cursor_buffer_list;
bool avoid_dmabufs;
// pipewire
@@ -158,12 +187,17 @@ struct xdpw_screencast_instance {
uint32_t max_framerate;
struct zwlr_screencopy_frame_v1 *wlr_frame;
struct xdpw_screencopy_frame_info screencopy_frame_info[2];
- bool with_cursor;
+ enum cursor_modes cursor_mode;
int err;
bool quit;
bool teardown;
enum buffer_type buffer_type;
+ // ext_screencopy
+ struct ext_screencopy_surface_v1 *surface_capture;
+ struct wl_array screencopy_cursor_frame_infos;
+ struct xdpw_cursor xdpw_cursor;
+
// fps limit
struct fps_limit_state fps_limit;
};
@@ -183,6 +217,7 @@ struct xdpw_wlr_output {
void randname(char *buf);
struct gbm_device *xdpw_gbm_device_create(drmDevice *device);
+void xdpw_buffer_apply_damage(struct xdpw_buffer *buffer, struct xdpw_damage *damage);
struct xdpw_buffer *xdpw_buffer_create(struct xdpw_screencast_instance *cast,
enum buffer_type buffer_type, struct xdpw_screencopy_frame_info *frame_info);
void xdpw_buffer_destroy(struct xdpw_buffer *buffer);
diff --git a/include/wlr_screencast.h b/include/wlr_screencast.h
index 83eaa493..6753f051 100644
--- a/include/wlr_screencast.h
+++ b/include/wlr_screencast.h
@@ -28,7 +28,9 @@ struct xdpw_wlr_output *xdpw_wlr_output_find(struct xdpw_screencast_context *ctx
struct xdpw_wlr_output *xdpw_wlr_output_chooser(struct xdpw_screencast_context *ctx);
void xdpw_wlr_frame_finish(struct xdpw_screencast_instance *cast);
-void xdpw_wlr_frame_start(struct xdpw_screencast_instance *cast);
void xdpw_wlr_register_cb(struct xdpw_screencast_instance *cast);
+void xdpw_wlr_ext_screencopy_surface_create(struct xdpw_screencast_instance *cast);
+void xdpw_wlr_ext_screencopy_surface_destroy(struct xdpw_screencast_instance *cast);
+void xdpw_wlr_handle_frame(struct xdpw_screencast_instance *cast);
#endif
diff --git a/protocols/ext-screencopy-v1.xml b/protocols/ext-screencopy-v1.xml
new file mode 100644
index 00000000..e1b3e60c
--- /dev/null
+++ b/protocols/ext-screencopy-v1.xml
@@ -0,0 +1,333 @@
+
+
+
+ Copyright © 2021-2022 Andri Yngvason
+
+ 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.
+
+
+
+ This protocol allows clients to ask the compositor to copy part of the
+ screen content to a client buffer.
+
+ Warning! The protocol described in this file is experimental and
+ backward incompatible changes may be made. Backward compatible changes
+ may be added together with the corresponding interface version bump.
+ Backward incompatible changes are done by bumping the version number in
+ the protocol and interface names and resetting the interface version.
+ Once the protocol is to be declared stable, the 'z' prefix and the
+ version number in the protocol and interface names are removed and the
+ interface version number is reset.
+
+
+
+
+ This object is a manager which offers requests to start capturing from a
+ source.
+
+
+
+
+
+
+
+
+ Create a capturing surface for an output
+
+ If the "render_cursors" flag is set, cursors shall be composited onto
+ the main surface. Otherwise, the compositor should try to leave them
+ out, if possible.
+
+
+
+
+
+
+
+
+
+ This object represents a surface that's being captured.
+
+ After a screencopy surface is created, buffer_info events will be emitted
+ from the compositor to tell the client which buffer types and formats are
+ supported for reading from the surface.
+
+ When the client knows all the buffer attributes, it can create a buffer,
+ attach it to the screencopy surface using the "attach_buffer" request,
+ set the buffer damage using the "damage_buffer" request and then call
+ the "commit" request.
+
+ After "commit" has been called, the next time that a buffer is committed
+ by the compositor, the contents of that buffer will be copied to the one
+ committed to the screencopy surface. A series of events will be generated,
+ ending with the "ready" event, which means that the buffer is ready to be
+ used and a buffer may be committed to the surface again.
+
+ The "failed" event may be sent at any time. When this happens, the client
+ must destroy the surface. Depending on the failure reason, the client can
+ create a new surface to replace it.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Provides information about buffer parameters that need to be used for
+ the main image. This event is sent for every supported buffer type
+ after the surface is created.
+
+ The stride parameter is invalid for dmabuf and may be set to 0.
+
+
+
+
+
+
+
+
+
+
+ Provides information about buffer parameters that need to be used for
+ the cursor image. This event is sent for every supported buffer type
+ after the surface is created, and it may be different for each
+ seat/input_type pair.
+
+ The default seat will be referred to as "default" within this protocol,
+ whether it be named so by the compositor or not.
+
+ The stride parameter is invalid for dmabuf and may be set to 0.
+
+
+
+
+
+
+
+
+
+
+
+
+ This event is sent once when all buffer info events have been sent.
+
+
+
+
+
+ Attach a buffer to the surface.
+
+
+
+
+
+
+ Apply damage to the buffer which is to be committed next.
+
+ This is for optimisation purposes. The compositor may use this
+ information to reduce copying.
+
+ The client must submit damage if it's using multiple buffers. Otherwise,
+ the server might not copy into damaged regions of the buffer.
+
+ These coordinates originate in the upper left corner of the buffer.
+
+
+
+
+
+
+
+
+
+ Attach a cursor buffer to the surface. The cursor for the given seat and
+ input type will be copied to the buffer.
+
+ The cursor buffer may exceed the dimensions specified in the
+ "cursor_buffer_info" event. The cursor image will be drawn in the top,
+ left corner of the buffer.
+
+
+
+
+
+
+
+
+ Apply damage to a named cursor buffer which is to be committed next.
+
+ The whole cursor buffer will be considered damaged.
+
+
+
+
+
+
+
+ Commit the screencopy surface.
+
+ The frame will be copied to the surface on next output commit. A ready
+ event is generated when the buffer is ready.
+
+ If the "on_damage" flag is set, the compositor should skip sending new
+ frames to the client until there is damage.
+
+
+
+
+
+
+ Destroys the surface. This request can be sent at any time by the
+ client.
+
+
+
+
+
+ This event is sent before the ready event and holds the output transform
+ of the source buffer.
+
+ Note: This only applies to the main buffer, not the cursor buffer. The
+ cursor buffer must always be sent without any rotation.
+
+
+
+
+
+
+ This event is sent before the ready event. It may be generated multiple
+ times for each commit.
+
+ The arguments describe a box around an area that has changed since the
+ last ready event.
+
+ These coordinates originate in the upper left corner of the buffer.
+
+
+
+
+
+
+
+
+
+ Sent when a cursor enters the captured surface. It shall be generated
+ before the "cursor_info" event when and only when a cursor enters the
+ surface.
+
+
+
+
+
+
+
+ Sent when a cursor leaves the captured surface. No "cursor_info" event
+ is generated for for the given cursor.
+
+
+
+
+
+
+
+ This event is generated for each cursor buffer that was attached to the
+ surface and for which the cursor is currently focused on the surface.
+ It is generated once for each cursor buffer before the ready event.
+
+ Cursors outside the surface do not get captured and no event will be
+ generated for them.
+
+ If the cursor image has changed, the cursor buffer will have been
+ updated and the "has_damage" argument will be set to 1; otherwise 0.
+
+ The given position is the position of the cursor's hotspot and it is
+ relative to the main buffer's top left corner in transformed buffer
+ pixel coordinates.
+
+ The hotspot coordinates are relative to the cursor buffers upper left
+ corner.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This event indicates that the attempted frame copy has failed.
+
+ After receiving this event, the client must destroy the object.
+
+
+
+
+
+
+ This event indicates the time at which the frame is committed to be
+ scanned out in system monotonic time.
+
+ The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples,
+ each component being an unsigned 32-bit value. Whole seconds are in
+ tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo,
+ and the additional fractional part in tv_nsec as nanoseconds. Hence,
+ for valid timestamps tv_nsec must be in [0, 999999999].
+
+
+
+
+
+
+
+
+ Called as soon as the frame is copied, indicating it is available
+ for reading.
+
+
+
+
diff --git a/protocols/meson.build b/protocols/meson.build
index f4a76ae2..cc630537 100644
--- a/protocols/meson.build
+++ b/protocols/meson.build
@@ -13,6 +13,7 @@ endif
client_protocols = [
wl_protocol_dir / 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml',
wl_protocol_dir / 'unstable/xdg-output/xdg-output-unstable-v1.xml',
+ 'ext-screencopy-v1.xml',
'wlr-screencopy-unstable-v1.xml',
]
diff --git a/src/screencast/pipewire_screencast.c b/src/screencast/pipewire_screencast.c
index 17f4c273..ea261307 100644
--- a/src/screencast/pipewire_screencast.c
+++ b/src/screencast/pipewire_screencast.c
@@ -14,6 +14,27 @@
#include "xdpw.h"
#include "logger.h"
+#define CURSOR_META_SIZE(width, height) \
+ (sizeof(struct spa_meta_cursor) + \
+ sizeof(struct spa_meta_bitmap) + \
+ width * height * 4)
+
+static void writeFrameData(void *pwFramePointer, void *wlrFramePointer,
+ uint32_t height, uint32_t stride, bool inverted) {
+ if (!inverted) {
+ memcpy(pwFramePointer, wlrFramePointer, height * stride);
+ return;
+ }
+
+ for (size_t i = 0; i < (size_t)height; ++i) {
+ void *flippedWlrRowPointer = wlrFramePointer + ((height - i - 1) * stride);
+ void *pwRowPointer = pwFramePointer + (i * stride);
+ memcpy(pwRowPointer, flippedWlrRowPointer, stride);
+ }
+
+ return;
+}
+
static struct spa_pod *build_buffer(struct spa_pod_builder *b, uint32_t blocks, uint32_t size,
uint32_t stride, uint32_t datatype) {
assert(blocks > 0);
@@ -164,6 +185,134 @@ static uint32_t build_formats(struct spa_pod_builder *b, struct xdpw_screencast_
return param_count;
}
+void xdpw_pwr_dequeue_buffer(struct xdpw_screencast_instance *cast) {
+ logprint(TRACE, "pipewire: dequeueing buffer");
+
+ assert(!cast->current_frame.pw_buffer);
+ if ((cast->current_frame.pw_buffer = pw_stream_dequeue_buffer(cast->stream)) == NULL) {
+ logprint(WARN, "pipewire: out of buffers");
+ return;
+ }
+
+ cast->current_frame.xdpw_buffer = cast->current_frame.pw_buffer->user_data;
+}
+
+static void pwr_handle_cursor(struct spa_meta_cursor *cursor, struct xdpw_screencast_instance *cast) {
+ logprint(TRACE, "pipewire: handle_cursor");
+ if (!cast->xdpw_cursor.present) {
+ cursor->id = 0;
+ logprint(TRACE, "pipewire: handle_cursor: cursor hidden");
+ return;
+ }
+
+ cursor->id = 1;
+ cursor->hotspot.x = cast->xdpw_cursor.hotspot_x;
+ cursor->hotspot.y = cast->xdpw_cursor.hotspot_y;
+ cursor->position.x = cast->xdpw_cursor.position_x;
+ cursor->position.y = cast->xdpw_cursor.position_y;
+ if (cast->xdpw_cursor.damaged) {
+ cursor->bitmap_offset = sizeof(struct spa_meta_cursor);
+
+ struct spa_meta_bitmap *cursor_bitmap;
+ cursor_bitmap = SPA_MEMBER(cursor, cursor->bitmap_offset, struct spa_meta_bitmap);
+ cursor_bitmap->format = xdpw_format_pw_from_drm_fourcc(cast->xdpw_cursor.xdpw_buffer->format);
+ cursor_bitmap->size.width = cast->xdpw_cursor.xdpw_buffer->width;
+ cursor_bitmap->size.height = cast->xdpw_cursor.xdpw_buffer->height;
+ cursor_bitmap->stride = cast->xdpw_cursor.xdpw_buffer->stride[0];
+ cursor_bitmap->offset = sizeof(struct spa_meta_bitmap);
+ uint32_t *bitmap_data = SPA_MEMBER(cursor_bitmap, cursor_bitmap->offset, uint32_t);
+ void *data = mmap(NULL, cast->xdpw_cursor.xdpw_buffer->size[0], PROT_READ | PROT_WRITE, MAP_SHARED, cast->xdpw_cursor.xdpw_buffer->fd[0], cast->xdpw_cursor.xdpw_buffer->offset[0]);
+ if (data != MAP_FAILED) {
+ writeFrameData(bitmap_data, data, cast->xdpw_cursor.xdpw_buffer->height, cast->xdpw_cursor.xdpw_buffer->stride[0], false);
+ munmap(data, cast->xdpw_cursor.xdpw_buffer->size[0]);
+ } else {
+ logprint(WARN, "pipewire: failed to mmap cursor buffer");
+ cursor_bitmap->offset = 0;
+ }
+ cast->xdpw_cursor.damaged = false;
+ } else {
+ logprint(TRACE, "pipewire: handle_cursor: cursor not damaged");
+ cursor->bitmap_offset = 0;
+ }
+}
+
+void xdpw_pwr_enqueue_buffer(struct xdpw_screencast_instance *cast) {
+ logprint(TRACE, "pipewire: enqueueing buffer");
+
+ if (!cast->current_frame.pw_buffer) {
+ logprint(WARN, "pipewire: no buffer to queue");
+ goto done;
+ }
+ struct pw_buffer *pw_buf = cast->current_frame.pw_buffer;
+ struct spa_buffer *spa_buf = pw_buf->buffer;
+ struct spa_data *d = spa_buf->datas;
+
+ bool buffer_corrupt = cast->frame_state != XDPW_FRAME_STATE_SUCCESS;
+
+ if (cast->current_frame.y_invert) {
+ //TODO: Flip buffer or set stride negative
+ buffer_corrupt = true;
+ cast->err = 1;
+ }
+
+ struct spa_meta_header *h;
+ if ((h = spa_buffer_find_meta_data(spa_buf, SPA_META_Header, sizeof(*h)))) {
+ h->pts = -1;
+ h->flags = buffer_corrupt ? SPA_META_HEADER_FLAG_CORRUPTED : 0;
+ h->seq = cast->seq++;
+ h->dts_offset = 0;
+ }
+
+ if (buffer_corrupt) {
+ for (uint32_t plane = 0; plane < spa_buf->n_datas; plane++) {
+ d[plane].chunk->flags = SPA_CHUNK_FLAG_CORRUPTED;
+ }
+ } else {
+ for (uint32_t plane = 0; plane < spa_buf->n_datas; plane++) {
+ d[plane].chunk->flags = SPA_CHUNK_FLAG_NONE;
+ }
+ }
+
+ struct spa_meta_cursor *cursor;
+ if ((cursor = spa_buffer_find_meta_data(spa_buf, SPA_META_Cursor, sizeof(*cursor)))) {
+ pwr_handle_cursor(cursor, cast);
+ }
+
+ logprint(TRACE, "********************");
+ for (uint32_t plane = 0; plane < spa_buf->n_datas; plane++) {
+ logprint(TRACE, "pipewire: plane %d", plane);
+ logprint(TRACE, "pipewire: fd %u", d[plane].fd);
+ logprint(TRACE, "pipewire: maxsize %d", d[plane].maxsize);
+ logprint(TRACE, "pipewire: size %d", d[plane].chunk->size);
+ logprint(TRACE, "pipewire: stride %d", d[plane].chunk->stride);
+ logprint(TRACE, "pipewire: offset %d", d[plane].chunk->offset);
+ logprint(TRACE, "pipewire: chunk flags %d", d[plane].chunk->flags);
+ }
+ logprint(TRACE, "pipewire: width %d", cast->current_frame.xdpw_buffer->width);
+ logprint(TRACE, "pipewire: height %d", cast->current_frame.xdpw_buffer->height);
+ logprint(TRACE, "pipewire: y_invert %d", cast->current_frame.y_invert);
+ logprint(TRACE, "********************");
+
+ pw_stream_queue_buffer(cast->stream, pw_buf);
+
+done:
+ cast->current_frame.xdpw_buffer = NULL;
+ cast->current_frame.pw_buffer = NULL;
+}
+
+void pwr_update_stream_param(struct xdpw_screencast_instance *cast) {
+ logprint(TRACE, "pipewire: stream update parameters");
+ struct pw_stream *stream = cast->stream;
+ uint8_t params_buffer[1024];
+ struct spa_pod_builder b =
+ SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer));
+ const struct spa_pod *params[2];
+
+ uint32_t n_params = build_formats(&b, cast, params);
+
+ pw_stream_update_params(stream, params, n_params);
+}
+
static void pwr_handle_stream_state_changed(void *data,
enum pw_stream_state old, enum pw_stream_state state, const char *error) {
struct xdpw_screencast_instance *cast = data;
@@ -176,9 +325,6 @@ static void pwr_handle_stream_state_changed(void *data,
switch (state) {
case PW_STREAM_STATE_STREAMING:
cast->pwr_stream_state = true;
- if (cast->frame_state == XDPW_FRAME_STATE_NONE) {
- xdpw_wlr_frame_start(cast);
- }
break;
case PW_STREAM_STATE_PAUSED:
if (old == PW_STREAM_STATE_STREAMING) {
@@ -196,7 +342,7 @@ static void pwr_handle_stream_param_changed(void *data, uint32_t id,
logprint(TRACE, "pipewire: stream parameters changed");
struct xdpw_screencast_instance *cast = data;
struct pw_stream *stream = cast->stream;
- uint8_t params_buffer[1024];
+ uint8_t params_buffer[2048];
struct spa_pod_builder b =
SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer));
const struct spa_pod *params[3];
@@ -291,15 +437,23 @@ static void pwr_handle_stream_param_changed(void *data, uint32_t id,
logprint(DEBUG, "pipewire: size: (%u, %u)", cast->pwr_format.size.width, cast->pwr_format.size.height);
logprint(DEBUG, "pipewire: max_framerate: (%u / %u)", cast->pwr_format.max_framerate.num, cast->pwr_format.max_framerate.denom);
- params[0] = build_buffer(&b, blocks, cast->screencopy_frame_info[cast->buffer_type].size,
+ uint32_t n_params = 0;
+ params[n_params++] = build_buffer(&b, blocks, cast->screencopy_frame_info[cast->buffer_type].size,
cast->screencopy_frame_info[cast->buffer_type].stride, data_type);
- params[1] = spa_pod_builder_add_object(&b,
+ params[n_params++] = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header),
SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header)));
- pw_stream_update_params(stream, params, 2);
+ if (cast->cursor_mode == METADATA) {
+ params[n_params++] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Cursor),
+ SPA_PARAM_META_size, SPA_POD_Int(CURSOR_META_SIZE(384, 384)));
+ }
+
+ pw_stream_update_params(stream, params, n_params);
}
static void pwr_handle_stream_add_buffer(void *data, struct pw_buffer *buffer) {
@@ -370,98 +524,45 @@ static void pwr_handle_stream_remove_buffer(void *data, struct pw_buffer *buffer
buffer->user_data = NULL;
}
-static const struct pw_stream_events pwr_stream_events = {
- PW_VERSION_STREAM_EVENTS,
- .state_changed = pwr_handle_stream_state_changed,
- .param_changed = pwr_handle_stream_param_changed,
- .add_buffer = pwr_handle_stream_add_buffer,
- .remove_buffer = pwr_handle_stream_remove_buffer,
-};
+static void pwr_handle_stream_on_process(void *data) {
+ struct xdpw_screencast_instance *cast = data;
-void xdpw_pwr_dequeue_buffer(struct xdpw_screencast_instance *cast) {
- logprint(TRACE, "pipewire: dequeueing buffer");
+ logprint(TRACE, "pipewire: on process event handle");
- assert(!cast->current_frame.pw_buffer);
- if ((cast->current_frame.pw_buffer = pw_stream_dequeue_buffer(cast->stream)) == NULL) {
- logprint(WARN, "pipewire: out of buffers");
+ if (!cast->pwr_stream_state) {
+ logprint(INFO, "pipewire: not streaming");
return;
}
- cast->current_frame.xdpw_buffer = cast->current_frame.pw_buffer->user_data;
-}
-
-void xdpw_pwr_enqueue_buffer(struct xdpw_screencast_instance *cast) {
- logprint(TRACE, "pipewire: enqueueing buffer");
-
- if (!cast->current_frame.pw_buffer) {
- logprint(WARN, "pipewire: no buffer to queue");
- goto done;
- }
- struct pw_buffer *pw_buf = cast->current_frame.pw_buffer;
- struct spa_buffer *spa_buf = pw_buf->buffer;
- struct spa_data *d = spa_buf->datas;
-
- bool buffer_corrupt = cast->frame_state != XDPW_FRAME_STATE_SUCCESS;
-
- if (cast->current_frame.y_invert) {
- //TODO: Flip buffer or set stride negative
- buffer_corrupt = true;
- cast->err = 1;
+ if (cast->current_frame.pw_buffer) {
+ logprint(WARN, "pipewire: buffer already exported");
+ return;
}
- logprint(TRACE, "********************");
- struct spa_meta_header *h;
- if ((h = spa_buffer_find_meta_data(spa_buf, SPA_META_Header, sizeof(*h)))) {
- h->pts = SPA_TIMESPEC_TO_NSEC(&cast->current_frame);
- h->flags = buffer_corrupt ? SPA_META_HEADER_FLAG_CORRUPTED : 0;
- h->seq = cast->seq++;
- h->dts_offset = 0;
- logprint(TRACE, "pipewire: timestamp %"PRId64, h->pts);
+ xdpw_pwr_dequeue_buffer(cast);
+ if (!cast->current_frame.pw_buffer) {
+ logprint(WARN, "pipewire: unable to export buffer");
+ return;
}
-
- if (buffer_corrupt) {
- for (uint32_t plane = 0; plane < spa_buf->n_datas; plane++) {
- d[plane].chunk->flags = SPA_CHUNK_FLAG_CORRUPTED;
- }
- } else {
- for (uint32_t plane = 0; plane < spa_buf->n_datas; plane++) {
- d[plane].chunk->flags = SPA_CHUNK_FLAG_NONE;
+ if (cast->seq > 0) {
+ uint64_t delay_ns = fps_limit_measure_end(&cast->fps_limit, cast->framerate);
+ if (delay_ns > 0) {
+ xdpw_add_timer(cast->ctx->state, delay_ns,
+ (xdpw_event_loop_timer_func_t) xdpw_wlr_handle_frame, cast);
+ return;
}
}
-
- for (uint32_t plane = 0; plane < spa_buf->n_datas; plane++) {
- logprint(TRACE, "pipewire: plane %d", plane);
- logprint(TRACE, "pipewire: fd %u", d[plane].fd);
- logprint(TRACE, "pipewire: maxsize %d", d[plane].maxsize);
- logprint(TRACE, "pipewire: size %d", d[plane].chunk->size);
- logprint(TRACE, "pipewire: stride %d", d[plane].chunk->stride);
- logprint(TRACE, "pipewire: offset %d", d[plane].chunk->offset);
- logprint(TRACE, "pipewire: chunk flags %d", d[plane].chunk->flags);
- }
- logprint(TRACE, "pipewire: width %d", cast->current_frame.xdpw_buffer->width);
- logprint(TRACE, "pipewire: height %d", cast->current_frame.xdpw_buffer->height);
- logprint(TRACE, "pipewire: y_invert %d", cast->current_frame.y_invert);
- logprint(TRACE, "********************");
-
- pw_stream_queue_buffer(cast->stream, pw_buf);
-
-done:
- cast->current_frame.xdpw_buffer = NULL;
- cast->current_frame.pw_buffer = NULL;
+ xdpw_wlr_handle_frame(cast);
}
-void pwr_update_stream_param(struct xdpw_screencast_instance *cast) {
- logprint(TRACE, "pipewire: stream update parameters");
- struct pw_stream *stream = cast->stream;
- uint8_t params_buffer[1024];
- struct spa_pod_builder b =
- SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer));
- const struct spa_pod *params[2];
-
- uint32_t n_params = build_formats(&b, cast, params);
-
- pw_stream_update_params(stream, params, n_params);
-}
+static const struct pw_stream_events pwr_stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .state_changed = pwr_handle_stream_state_changed,
+ .param_changed = pwr_handle_stream_param_changed,
+ .add_buffer = pwr_handle_stream_add_buffer,
+ .remove_buffer = pwr_handle_stream_remove_buffer,
+ .process = pwr_handle_stream_on_process,
+};
void xdpw_pwr_stream_create(struct xdpw_screencast_instance *cast) {
struct xdpw_screencast_context *ctx = cast->ctx;
@@ -494,8 +595,7 @@ void xdpw_pwr_stream_create(struct xdpw_screencast_instance *cast) {
pw_stream_connect(cast->stream,
PW_DIRECTION_OUTPUT,
PW_ID_ANY,
- (PW_STREAM_FLAG_DRIVER |
- PW_STREAM_FLAG_ALLOC_BUFFERS),
+ PW_STREAM_FLAG_ALLOC_BUFFERS,
params, param_count);
}
diff --git a/src/screencast/screencast.c b/src/screencast/screencast.c
index 04a5676c..56e17e77 100644
--- a/src/screencast/screencast.c
+++ b/src/screencast/screencast.c
@@ -48,7 +48,7 @@ void exec_with_shell(char *command) {
}
void xdpw_screencast_instance_init(struct xdpw_screencast_context *ctx,
- struct xdpw_screencast_instance *cast, struct xdpw_wlr_output *out, bool with_cursor) {
+ struct xdpw_screencast_instance *cast, struct xdpw_wlr_output *out, enum cursor_modes cursor_mode) {
// only run exec_before if there's no other instance running that already ran it
if (wl_list_empty(&ctx->screencast_instances)) {
@@ -68,12 +68,13 @@ void xdpw_screencast_instance_init(struct xdpw_screencast_context *ctx,
cast->max_framerate = (uint32_t)out->framerate;
}
cast->framerate = cast->max_framerate;
- cast->with_cursor = with_cursor;
+ cast->cursor_mode = cursor_mode;
cast->refcount = 1;
cast->node_id = SPA_ID_INVALID;
cast->avoid_dmabufs = false;
cast->teardown = false;
wl_list_init(&cast->buffer_list);
+ wl_list_init(&cast->cursor_buffer_list);
logprint(INFO, "xdpw: screencast instance %p has %d references", cast, cast->refcount);
wl_list_insert(&ctx->screencast_instances, &cast->link);
logprint(INFO, "xdpw: %d active screencast instances",
@@ -84,6 +85,10 @@ void xdpw_screencast_instance_destroy(struct xdpw_screencast_instance *cast) {
assert(cast->refcount == 0); // Fails assert if called by screencast_finish
logprint(DEBUG, "xdpw: destroying cast instance");
+ if (cast->ctx->ext_screencopy_manager) {
+ xdpw_wlr_ext_screencopy_surface_destroy(cast);
+ }
+
// make sure this is the last running instance that is being destroyed
if (wl_list_length(&cast->link) == 1) {
char *exec_after = cast->ctx->state->config->screencast_conf.exec_after;
@@ -108,7 +113,7 @@ void xdpw_screencast_instance_teardown(struct xdpw_screencast_instance *cast) {
}
}
-bool setup_outputs(struct xdpw_screencast_context *ctx, struct xdpw_session *sess, bool with_cursor) {
+bool setup_outputs(struct xdpw_screencast_context *ctx, struct xdpw_session *sess, enum cursor_modes cursor_mode) {
struct xdpw_wlr_output *output, *tmp_o;
wl_list_for_each_reverse_safe(output, tmp_o, &ctx->output_list, link) {
@@ -150,32 +155,44 @@ bool setup_outputs(struct xdpw_screencast_context *ctx, struct xdpw_session *ses
if (!sess->screencast_instance) {
sess->screencast_instance = calloc(1, sizeof(struct xdpw_screencast_instance));
xdpw_screencast_instance_init(ctx, sess->screencast_instance,
- out, with_cursor);
+ out, cursor_mode);
}
logprint(INFO, "wlroots: output: %s",
sess->screencast_instance->target_output->name);
+ logprint(INFO, "wlroots: cursor_mode: %d",
+ sess->screencast_instance->cursor_mode);
return true;
}
static int start_screencast(struct xdpw_screencast_instance *cast) {
- xdpw_wlr_register_cb(cast);
-
- // process at least one frame so that we know
- // some of the metadata required for the pipewire
- // remote state connected event
- wl_display_dispatch(cast->ctx->state->wl_display);
- wl_display_roundtrip(cast->ctx->state->wl_display);
-
- if (cast->screencopy_frame_info[WL_SHM].format == DRM_FORMAT_INVALID ||
- (cast->ctx->state->screencast_version >= 3 &&
- cast->screencopy_frame_info[DMABUF].format == DRM_FORMAT_INVALID)) {
- logprint(INFO, "wlroots: unable to receive a valid format from wlr_screencopy");
- return -1;
- }
+ if (cast->ctx->ext_screencopy_manager) {
+ xdpw_wlr_ext_screencopy_surface_create(cast);
+
+ // process at least one frame so that we know
+ // some of the metadata required for the pipewire
+ // remote state connected event
+ wl_display_dispatch(cast->ctx->state->wl_display);
+ wl_display_roundtrip(cast->ctx->state->wl_display);
+ } else {
+ xdpw_wlr_register_cb(cast);
+
+ // process at least one frame so that we know
+ // some of the metadata required for the pipewire
+ // remote state connected event
+ wl_display_dispatch(cast->ctx->state->wl_display);
+ wl_display_roundtrip(cast->ctx->state->wl_display);
+
+ if (cast->screencopy_frame_info[WL_SHM].format == DRM_FORMAT_INVALID ||
+ (cast->ctx->state->screencast_version >= 3 &&
+ cast->screencopy_frame_info[DMABUF].format == DRM_FORMAT_INVALID)) {
+ logprint(INFO, "wlroots: unable to receive a valid format from wlr_screencopy");
+ return -1;
+ }
- xdpw_pwr_stream_create(cast);
+ xdpw_pwr_stream_create(cast);
+ }
cast->initialized = true;
return 0;
@@ -277,7 +294,7 @@ static int method_screencast_select_sources(sd_bus_message *msg, void *data,
logprint(INFO, "dbus: select sources method invoked");
// default to embedded cursor mode if not specified
- bool cursor_embedded = true;
+ uint32_t cursor_mode = EMBEDDED;
char *request_handle, *session_handle, *app_id;
ret = sd_bus_message_read(msg, "oos", &request_handle, &session_handle, &app_id);
@@ -314,14 +331,14 @@ static int method_screencast_select_sources(sd_bus_message *msg, void *data,
}
logprint(INFO, "dbus: option types:%x", mask);
} else if (strcmp(key, "cursor_mode") == 0) {
- uint32_t cursor_mode;
sd_bus_message_read(msg, "v", "u", &cursor_mode);
- if (cursor_mode & HIDDEN) {
- cursor_embedded = false;
- }
+ logprint(INFO, "dbus: option cursor_mode:%x", cursor_mode);
if (cursor_mode & METADATA) {
- logprint(ERROR, "dbus: unsupported cursor mode requested, cancelling");
- goto error;
+ cursor_mode = METADATA;
+ } else if (cursor_mode & EMBEDDED) {
+ cursor_mode = EMBEDDED;
+ } else if (cursor_mode & HIDDEN) {
+ cursor_mode = HIDDEN;
}
logprint(INFO, "dbus: option cursor_mode:%x", cursor_mode);
} else {
@@ -346,7 +363,7 @@ static int method_screencast_select_sources(sd_bus_message *msg, void *data,
wl_list_for_each_reverse_safe(sess, tmp_s, &state->xdpw_sessions, link) {
if (strcmp(sess->session_handle, session_handle) == 0) {
logprint(DEBUG, "dbus: select sources: found matching session %s", sess->session_handle);
- output_selection_canceled = !setup_outputs(ctx, sess, cursor_embedded);
+ output_selection_canceled = !setup_outputs(ctx, sess, cursor_mode);
}
}
@@ -368,29 +385,6 @@ static int method_screencast_select_sources(sd_bus_message *msg, void *data,
}
sd_bus_message_unref(reply);
return 0;
-
-error:
- wl_list_for_each_reverse_safe(sess, tmp_s, &state->xdpw_sessions, link) {
- if (strcmp(sess->session_handle, session_handle) == 0) {
- logprint(DEBUG, "dbus: select sources error: destroying matching session %s", sess->session_handle);
- xdpw_session_destroy(sess);
- }
- }
-
- ret = sd_bus_message_new_method_return(msg, &reply);
- if (ret < 0) {
- return ret;
- }
- ret = sd_bus_message_append(reply, "ua{sv}", PORTAL_RESPONSE_CANCELLED, 0);
- if (ret < 0) {
- return ret;
- }
- ret = sd_bus_send(NULL, reply, NULL);
- if (ret < 0) {
- return ret;
- }
- sd_bus_message_unref(reply);
- return -1;
}
static int method_screencast_start(sd_bus_message *msg, void *data,
@@ -528,6 +522,7 @@ int xdpw_screencast_init(struct xdpw_state *state) {
goto fail_screencopy;
}
+ logprint(INFO, "cursor_modes %x", state->screencast_cursor_modes);
return sd_bus_add_object_vtable(state->bus, &slot, object_path, interface_name,
screencast_vtable, state);
diff --git a/src/screencast/screencast_common.c b/src/screencast/screencast_common.c
index 8c00c507..d54775b0 100644
--- a/src/screencast/screencast_common.c
+++ b/src/screencast/screencast_common.c
@@ -102,6 +102,36 @@ static struct wl_buffer *import_wl_shm_buffer(struct xdpw_screencast_instance *c
return buffer;
}
+void xdpw_buffer_apply_damage(struct xdpw_buffer *buffer, struct xdpw_damage *damage) {
+ if (!damage) {
+ buffer->damage.x = 0;
+ buffer->damage.y = 0;
+ buffer->damage.width = buffer->width;
+ buffer->damage.height = buffer->height;
+ return;
+ }
+
+ if (damage->x == 0 && damage->y == 0 && damage->width == 0 && damage->height == 0) {
+ buffer->damage.x = damage->x;
+ buffer->damage.y = damage->y;
+ buffer->damage.width = damage->width;
+ buffer->damage.height = damage->height;
+ return;
+ }
+
+ uint32_t x, y, width, height;
+
+ x = SPA_MIN(buffer->damage.x, damage->x);
+ y = SPA_MIN(buffer->damage.y, damage->y);
+ width = SPA_MAX(buffer->damage.x + buffer->damage.width, damage->x + damage->width) - x;
+ height = SPA_MAX(buffer->damage.y + buffer->damage.height, damage->y + damage->height) - y;
+
+ buffer->damage.x = x;
+ buffer->damage.y = y;
+ buffer->damage.width = width;
+ buffer->damage.height = height;
+}
+
struct xdpw_buffer *xdpw_buffer_create(struct xdpw_screencast_instance *cast,
enum buffer_type buffer_type, struct xdpw_screencopy_frame_info *frame_info) {
struct xdpw_buffer *buffer = calloc(1, sizeof(struct xdpw_buffer));
@@ -206,6 +236,7 @@ struct xdpw_buffer *xdpw_buffer_create(struct xdpw_screencast_instance *cast,
}
}
+ xdpw_buffer_apply_damage(buffer, NULL);
return buffer;
}
diff --git a/src/screencast/wlr_screencast.c b/src/screencast/wlr_screencast.c
index 8d645539..26f426b4 100644
--- a/src/screencast/wlr_screencast.c
+++ b/src/screencast/wlr_screencast.c
@@ -3,6 +3,7 @@
#include "linux-dmabuf-unstable-v1-client-protocol.h"
#include "wlr-screencopy-unstable-v1-client-protocol.h"
#include "xdg-output-unstable-v1-client-protocol.h"
+#include "ext-screencopy-v1-client-protocol.h"
#include
#include
#include
@@ -32,6 +33,22 @@ void wlr_frame_free(struct xdpw_screencast_instance *cast) {
logprint(TRACE, "wlroots: frame destroyed");
}
+static void xdpw_wlr_frame_start(struct xdpw_screencast_instance *cast) {
+ logprint(TRACE, "wlroots: start screencopy");
+ if (cast->quit || cast->err) {
+ xdpw_screencast_instance_destroy(cast);
+ return;
+ }
+
+ if (cast->initialized && !cast->pwr_stream_state) {
+ cast->frame_state = XDPW_FRAME_STATE_NONE;
+ return;
+ }
+
+ cast->frame_state = XDPW_FRAME_STATE_STARTED;
+ xdpw_wlr_register_cb(cast);
+}
+
void xdpw_wlr_frame_finish(struct xdpw_screencast_instance *cast) {
logprint(TRACE, "wlroots: finish screencopy");
@@ -60,30 +77,7 @@ void xdpw_wlr_frame_finish(struct xdpw_screencast_instance *cast) {
if (cast->frame_state == XDPW_FRAME_STATE_SUCCESS) {
xdpw_pwr_enqueue_buffer(cast);
- uint64_t delay_ns = fps_limit_measure_end(&cast->fps_limit, cast->framerate);
- if (delay_ns > 0) {
- xdpw_add_timer(cast->ctx->state, delay_ns,
- (xdpw_event_loop_timer_func_t) xdpw_wlr_frame_start, cast);
- return;
- }
}
- xdpw_wlr_frame_start(cast);
-}
-
-void xdpw_wlr_frame_start(struct xdpw_screencast_instance *cast) {
- logprint(TRACE, "wlroots: start screencopy");
- if (cast->quit || cast->err) {
- xdpw_screencast_instance_destroy(cast);
- return;
- }
-
- if (cast->initialized && !cast->pwr_stream_state) {
- cast->frame_state = XDPW_FRAME_STATE_NONE;
- return;
- }
-
- cast->frame_state = XDPW_FRAME_STATE_STARTED;
- xdpw_wlr_register_cb(cast);
}
static void wlr_frame_buffer_done(void *data,
@@ -150,10 +144,6 @@ static void wlr_frame_buffer_done(void *data,
return;
}
- if (!cast->current_frame.xdpw_buffer) {
- xdpw_pwr_dequeue_buffer(cast);
- }
-
if (!cast->current_frame.xdpw_buffer) {
logprint(WARN, "wlroots: no current buffer");
xdpw_wlr_frame_finish(cast);
@@ -250,13 +240,284 @@ static const struct zwlr_screencopy_frame_v1_listener wlr_frame_listener = {
void xdpw_wlr_register_cb(struct xdpw_screencast_instance *cast) {
cast->frame_callback = zwlr_screencopy_manager_v1_capture_output(
- cast->ctx->screencopy_manager, cast->with_cursor, cast->target_output->output);
+ cast->ctx->screencopy_manager, cast->cursor_mode == EMBEDDED ? 1 : 0, cast->target_output->output);
zwlr_screencopy_frame_v1_add_listener(cast->frame_callback,
&wlr_frame_listener, cast);
logprint(TRACE, "wlroots: callbacks registered");
}
+static void ext_surface_buffer_info(void *data, struct ext_screencopy_surface_v1 *surface,
+ uint32_t type, uint32_t drm_format, uint32_t width, uint32_t height, uint32_t stride) {
+ struct xdpw_screencast_instance *cast = data;
+
+ logprint(TRACE, "wlroots: buffer_info event handler");
+
+ cast->screencopy_frame_info[type].format = drm_format;
+ cast->screencopy_frame_info[type].width = width;
+ cast->screencopy_frame_info[type].height = height;
+ cast->screencopy_frame_info[type].stride = stride;
+ cast->screencopy_frame_info[type].size = stride * height;
+}
+
+static void ext_surface_cursor_buffer_info(void *data, struct ext_screencopy_surface_v1 *surface,
+ const char* seat_name, uint32_t input_type, uint32_t buffer_type,
+ uint32_t drm_format, uint32_t width, uint32_t height, uint32_t stride) {
+ struct xdpw_screencast_instance *cast = data;
+
+ logprint(TRACE, "wlroots: cursor_buffer_info event handler");
+
+
+ struct xdpw_screencopy_cursor_frame_info *screencopy_cursor_frame_info;
+ wl_array_for_each(screencopy_cursor_frame_info, &cast->screencopy_cursor_frame_infos) {
+ if (screencopy_cursor_frame_info->input_type == input_type &&
+ strcmp(screencopy_cursor_frame_info->seat_name, seat_name) == 0) {
+ goto assign_frame_info;
+ }
+ }
+
+ screencopy_cursor_frame_info = wl_array_add(&cast->screencopy_cursor_frame_infos, sizeof(struct xdpw_screencopy_cursor_frame_info));
+ if (!screencopy_cursor_frame_info)
+ return;
+ memset(screencopy_cursor_frame_info, 0, sizeof(struct xdpw_screencopy_cursor_frame_info));
+ screencopy_cursor_frame_info->seat_name = strdup(seat_name);
+ screencopy_cursor_frame_info->input_type = input_type;
+
+assign_frame_info:
+ screencopy_cursor_frame_info->frame_info[buffer_type].format = drm_format;
+ screencopy_cursor_frame_info->frame_info[buffer_type].width = width;
+ screencopy_cursor_frame_info->frame_info[buffer_type].height = height;
+ screencopy_cursor_frame_info->frame_info[buffer_type].stride = stride;
+ screencopy_cursor_frame_info->frame_info[buffer_type].size = stride * height;
+}
+
+static void ext_surface_init_done(void *data, struct ext_screencopy_surface_v1 *surface) {
+ struct xdpw_screencast_instance *cast = data;
+
+ logprint(TRACE, "wlroots: init_done event handler");
+
+ if (cast->cursor_mode == METADATA) {
+ struct xdpw_screencopy_cursor_frame_info *screencopy_cursor_frame_info;
+ wl_array_for_each(screencopy_cursor_frame_info, &cast->screencopy_cursor_frame_infos) {
+ if (screencopy_cursor_frame_info->input_type == EXT_SCREENCOPY_INPUT_TYPE_POINTER &&
+ screencopy_cursor_frame_info->frame_info[WL_SHM].format != 0) {
+ cast->xdpw_cursor.seat_name = strdup(screencopy_cursor_frame_info->seat_name);
+ cast->xdpw_cursor.input_type = screencopy_cursor_frame_info->input_type;
+ cast->xdpw_cursor.xdpw_buffer = xdpw_buffer_create(cast, WL_SHM, &screencopy_cursor_frame_info->frame_info[WL_SHM]);
+ cast->xdpw_cursor.damaged = true;
+ wl_list_insert(&cast->cursor_buffer_list, &cast->xdpw_cursor.xdpw_buffer->link);
+ break;
+ }
+ }
+
+ }
+ if (cast->stream) {
+ pwr_update_stream_param(cast);
+ } else {
+ xdpw_pwr_stream_create(cast);
+ }
+}
+
+static void ext_surface_transform(void *data, struct ext_screencopy_surface_v1 *surface,
+ int32_t transform) {
+ logprint(TRACE, "wlroots: transform event handler");
+}
+
+static void ext_surface_damage(void *data, struct ext_screencopy_surface_v1 *surface,
+ uint32_t x, uint32_t y, uint32_t width, uint32_t height) {
+ struct xdpw_screencast_instance *cast = data;
+
+ logprint(TRACE, "wlroots: damage event handler");
+ cast->current_frame.damage.x = x;
+ cast->current_frame.damage.y = y;
+ cast->current_frame.damage.width = width;
+ cast->current_frame.damage.height = height;
+
+ struct xdpw_damage damage = {
+ .x = x,
+ .y = y,
+ .width = width,
+ .height = height,
+ };
+ struct xdpw_buffer *buffer;
+ wl_list_for_each(buffer, &cast->buffer_list, link) {
+ xdpw_buffer_apply_damage(buffer, &damage);
+ }
+
+ struct xdpw_damage *current_buffer_damage = &cast->current_frame.xdpw_buffer->damage;
+ current_buffer_damage->x = 0;
+ current_buffer_damage->y = 0;
+ current_buffer_damage->width = 0;
+ current_buffer_damage->height = 0;
+
+ logprint(TRACE, "wlroots: damage %u:%u (%u x %u)", x, y, width, height);
+}
+
+static void ext_surface_cursor_enter(void *data, struct ext_screencopy_surface_v1 *surface,
+ const char* seat_name, uint32_t input_type) {
+ struct xdpw_screencast_instance *cast = data;
+
+ logprint(TRACE, "wlroots: cursor_enter event handler");
+
+ if (!cast->xdpw_cursor.seat_name || strcmp(cast->xdpw_cursor.seat_name, seat_name) != 0 ||
+ cast->xdpw_cursor.input_type != input_type) {
+ return;
+ }
+ cast->xdpw_cursor.present = true;
+}
+
+static void ext_surface_cursor_leave(void *data, struct ext_screencopy_surface_v1 *surface,
+ const char* seat_name, uint32_t input_type) {
+ struct xdpw_screencast_instance *cast = data;
+
+ logprint(TRACE, "wlroots: cursor_leave event handler");
+
+ if (!cast->xdpw_cursor.seat_name || strcmp(cast->xdpw_cursor.seat_name, seat_name) != 0 ||
+ cast->xdpw_cursor.input_type != input_type) {
+ return;
+ }
+ cast->xdpw_cursor.present = false;
+}
+
+static void ext_surface_cursor_info(void *data, struct ext_screencopy_surface_v1 *surface,
+ const char* seat_name, uint32_t input_type, int32_t has_damage,
+ int32_t position_x, int32_t position_y, int32_t width, int32_t height,
+ int32_t hotspot_x, int32_t hotspot_y) {
+ struct xdpw_screencast_instance *cast = data;
+
+ logprint(TRACE, "wlroots: cursor_info event handler");
+
+ if (!cast->xdpw_cursor.seat_name || strcmp(cast->xdpw_cursor.seat_name, seat_name) != 0 ||
+ cast->xdpw_cursor.input_type != input_type) {
+ return;
+ }
+
+ cast->xdpw_cursor.position_x = position_x;
+ cast->xdpw_cursor.position_y = position_y;
+ cast->xdpw_cursor.width = width;
+ cast->xdpw_cursor.height = height;
+ cast->xdpw_cursor.hotspot_x = hotspot_x;
+ cast->xdpw_cursor.hotspot_y = hotspot_y;
+ cast->xdpw_cursor.damaged = has_damage;
+}
+
+static void ext_surface_failed(void *data, struct ext_screencopy_surface_v1 *surface,
+ uint32_t reason) {
+ struct xdpw_screencast_instance *cast = data;
+
+ logprint(TRACE, "wlroots: failed event handler");
+
+ enum failure_reason {
+ EXT_SCREENCOPY_FAILURE_REASON_UNSPEC = 0,
+ EXT_SCREENCOPY_FAILURE_REASON_INVALID_MAIN_BUFFER,
+ EXT_SCREENCOPY_FAILURE_REASON_INVALID_CURSOR_BUFFER,
+ EXT_SCREENCOPY_FAILURE_REASON_OUTPUT_MISSING,
+ EXT_SCREENCOPY_FAILURE_REASON_OUTPUT_DISABLED,
+ EXT_SCREENCOPY_FAILURE_REASON_UNKOWN_INPUT,
+ };
+
+ switch (reason) {
+ case EXT_SCREENCOPY_FAILURE_REASON_INVALID_MAIN_BUFFER:
+ case EXT_SCREENCOPY_FAILURE_REASON_INVALID_CURSOR_BUFFER:
+ ext_screencopy_surface_v1_destroy(cast->surface_capture);
+ xdpw_wlr_ext_screencopy_surface_create(cast);
+ break;
+ default:
+ xdpw_screencast_instance_destroy(cast);
+ }
+}
+
+static void ext_surface_commit_time(void *data, struct ext_screencopy_surface_v1 *surface,
+ uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) {
+ struct xdpw_screencast_instance *cast = data;
+
+ logprint(TRACE, "wlroots: commit_time event handler");
+
+ cast->current_frame.tv_sec = ((((uint64_t)tv_sec_hi) << 32) | tv_sec_lo);
+ cast->current_frame.tv_nsec = tv_nsec;
+ logprint(TRACE, "wlroots: timestamp %"PRIu64":%"PRIu32, cast->current_frame.tv_sec, cast->current_frame.tv_nsec);
+}
+
+static void ext_surface_ready(void *data, struct ext_screencopy_surface_v1 *surface) {
+ struct xdpw_screencast_instance *cast = data;
+
+ logprint(TRACE, "wlroots: ready event handler");
+
+ xdpw_pwr_enqueue_buffer(cast);
+}
+
+static const struct ext_screencopy_surface_v1_listener ext_screencopy_surface_listener = {
+ .buffer_info = ext_surface_buffer_info,
+ .cursor_buffer_info = ext_surface_cursor_buffer_info,
+ .init_done = ext_surface_init_done,
+ .transform = ext_surface_transform,
+ .damage = ext_surface_damage,
+ .cursor_enter = ext_surface_cursor_enter,
+ .cursor_leave = ext_surface_cursor_leave,
+ .cursor_info = ext_surface_cursor_info,
+ .failed = ext_surface_failed,
+ .commit_time = ext_surface_commit_time,
+ .ready = ext_surface_ready,
+};
+
+void wlr_ext_screencopy_frame_submit(struct xdpw_screencast_instance *cast) {
+ enum ext_screencopy_surface_v1_options {
+ EXT_SCREENCOPY_OPTIONS_ON_DAMAGE = 1,
+ };
+
+ const struct xdpw_buffer *current_buffer = cast->current_frame.xdpw_buffer;
+ ext_screencopy_surface_v1_attach_buffer(cast->surface_capture, current_buffer->buffer);
+ ext_screencopy_surface_v1_damage_buffer(cast->surface_capture, current_buffer->damage.x, current_buffer->damage.y,
+ current_buffer->damage.width, current_buffer->damage.height);
+ if (cast->cursor_mode == METADATA && cast->xdpw_cursor.seat_name) {
+ logprint(TRACE, "wlroots: attach cursor buffer");
+ ext_screencopy_surface_v1_attach_cursor_buffer(cast->surface_capture, cast->xdpw_cursor.xdpw_buffer->buffer,
+ cast->xdpw_cursor.seat_name, cast->xdpw_cursor.input_type);
+ if (cast->xdpw_cursor.damaged) {
+ ext_screencopy_surface_v1_damage_cursor_buffer(cast->surface_capture,
+ cast->xdpw_cursor.seat_name, cast->xdpw_cursor.input_type);
+ }
+ }
+ ext_screencopy_surface_v1_commit(cast->surface_capture, EXT_SCREENCOPY_OPTIONS_ON_DAMAGE);
+ logprint(TRACE, "wlroots: frame commited");
+ fps_limit_measure_start(&cast->fps_limit, cast->framerate);
+}
+
+void xdpw_wlr_ext_screencopy_surface_create(struct xdpw_screencast_instance *cast) {
+ enum ext_screencopy_manager_v1_options options = 0;
+ if (cast->cursor_mode == EMBEDDED) {
+ options = 1;
+ }
+ wl_array_init(&cast->screencopy_cursor_frame_infos);
+
+ cast->surface_capture = ext_screencopy_manager_v1_capture_output(cast->ctx->ext_screencopy_manager,
+ cast->target_output->output, options);
+
+ ext_screencopy_surface_v1_add_listener(cast->surface_capture, &ext_screencopy_surface_listener, cast);
+}
+
+void xdpw_wlr_ext_screencopy_surface_destroy(struct xdpw_screencast_instance *cast) {
+ struct xdpw_screencopy_cursor_frame_info *screencopy_cursor_frame_info;
+ wl_array_for_each(screencopy_cursor_frame_info, &cast->screencopy_cursor_frame_infos) {
+ free(screencopy_cursor_frame_info->seat_name);
+ }
+ wl_array_release(&cast->screencopy_cursor_frame_infos);
+
+ if (cast->cursor_mode == METADATA) {
+ xdpw_buffer_destroy(cast->xdpw_cursor.xdpw_buffer);
+ free(cast->xdpw_cursor.seat_name);
+ }
+ ext_screencopy_surface_v1_destroy(cast->surface_capture);
+}
+
+void xdpw_wlr_handle_frame(struct xdpw_screencast_instance *cast) {
+ if (cast->ctx->ext_screencopy_manager) {
+ wlr_ext_screencopy_frame_submit(cast);
+ } else {
+ xdpw_wlr_frame_start(cast);
+ }
+}
+
static void wlr_output_handle_geometry(void *data, struct wl_output *wl_output,
int32_t x, int32_t y, int32_t phys_width, int32_t phys_height,
int32_t subpixel, const char *make, const char *model, int32_t transform) {
@@ -763,6 +1024,12 @@ static void wlr_registry_handle_add(void *data, struct wl_registry *reg,
reg, id, &zwlr_screencopy_manager_v1_interface, version);
}
+ if (!strcmp(interface, ext_screencopy_manager_v1_interface.name)) {
+ logprint(DEBUG, "wlroots: |-- registered to interface %s (Version %u)", interface, 1);
+ ctx->ext_screencopy_manager = wl_registry_bind(
+ reg, id, &ext_screencopy_manager_v1_interface, 1);
+ }
+
if (strcmp(interface, wl_shm_interface.name) == 0) {
logprint(DEBUG, "wlroots: |-- registered to interface %s (Version %u)", interface, WL_SHM_VERSION);
ctx->shm = wl_registry_bind(reg, id, &wl_shm_interface, WL_SHM_VERSION);
@@ -878,6 +1145,11 @@ int xdpw_wlr_screencopy_init(struct xdpw_state *state) {
}
}
+ // offer cursor_mode METADATA
+ if (ctx->ext_screencopy_manager) {
+ ctx->state->screencast_cursor_modes |= METADATA;
+ }
+
return 0;
}