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; }