From 2a31d2d9229dabeeff33b90cf5c20e1f036de988 Mon Sep 17 00:00:00 2001 From: Dan Shick Date: Fri, 24 Jan 2020 17:31:01 -0500 Subject: [PATCH] Add thread for wlr screensharing --- .gitignore | 3 + include/pipewire_screencast.h | 18 ++ include/screencast.h | 18 ++ include/screencast_common.h | 107 +++++++ include/wlr_screencast.h | 29 ++ include/xdpw.h | 1 + meson.build | 30 +- protocols/meson.build | 44 +++ protocols/wlr-screencopy-unstable-v1.xml | 207 +++++++++++++ main.c => src/core/main.c | 1 + request.c => src/core/request.c | 16 + session.c => src/core/session.c | 16 + src/screencast/pipewire_screencast.c | 227 ++++++++++++++ src/screencast/screencast.c | 323 ++++++++++++++++++++ src/screencast/screencast_common.c | 35 +++ src/screencast/wlr_screencast.c | 278 +++++++++++++++++ screenshot.c => src/screenshot/screenshot.c | 0 17 files changed, 1345 insertions(+), 8 deletions(-) create mode 100644 include/pipewire_screencast.h create mode 100644 include/screencast.h create mode 100644 include/screencast_common.h create mode 100644 include/wlr_screencast.h create mode 100644 protocols/meson.build create mode 100644 protocols/wlr-screencopy-unstable-v1.xml rename main.c => src/core/main.c (97%) rename request.c => src/core/request.c (80%) rename session.c => src/core/session.c (81%) create mode 100644 src/screencast/pipewire_screencast.c create mode 100644 src/screencast/screencast.c create mode 100644 src/screencast/screencast_common.c create mode 100644 src/screencast/wlr_screencast.c rename screenshot.c => src/screenshot/screenshot.c (100%) diff --git a/.gitignore b/.gitignore index c6127b3..6b8824a 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,6 @@ modules.order Module.symvers Mkfile.old dkms.conf + +# build folder +build/ diff --git a/include/pipewire_screencast.h b/include/pipewire_screencast.h new file mode 100644 index 0000000..3c19743 --- /dev/null +++ b/include/pipewire_screencast.h @@ -0,0 +1,18 @@ +#ifndef PIPEWIRE_SCREENCAST_H +#define PIPEWIRE_SCREENCAST_H + +#include +#include +#include +#include +#include +#include +#include "wlr_screencast.h" +#include "screencast_common.h" + +#define BUFFERS 1 +#define ALIGN 16 + +void *pwr_start(void *data); + +#endif diff --git a/include/screencast.h b/include/screencast.h new file mode 100644 index 0000000..6a9aeda --- /dev/null +++ b/include/screencast.h @@ -0,0 +1,18 @@ +#ifndef SCREENCAST_H +#define SCREENCAST_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pipewire_screencast.h" +#include "screencast_common.h" +#include "wlr_screencast.h" +#include "xdpw.h" + +#endif diff --git a/include/screencast_common.h b/include/screencast_common.h new file mode 100644 index 0000000..a1613a4 --- /dev/null +++ b/include/screencast_common.h @@ -0,0 +1,107 @@ +#ifndef SCREENCAST_COMMON_H +#define SCREENCAST_COMMON_H + +#include +#include +#include +#include +#include + +// Disableable logger +//#define logger(...) printf(__VA_ARGS__) +#define logger(...) + +struct damage { + uint32_t x; + uint32_t y; + uint32_t width; + uint32_t height; +}; + +struct simple_frame { + uint32_t width; + uint32_t height; + uint32_t size; + uint32_t stride; + bool y_invert; + uint64_t tv_sec; + uint32_t tv_nsec; + enum wl_shm_format format; + struct damage *damage; + + struct wl_buffer *buffer; + void *data; +}; + +struct pwr_type { + struct spa_type_media_type media_type; + struct spa_type_media_subtype media_subtype; + struct spa_type_format_video format_video; + struct spa_type_video_format video_format; + uint32_t meta_cursor; +}; + +struct screencast_context { + + // pipewire + struct pwr_type type; + + struct pw_main_loop *loop; + struct spa_source *event; + struct pw_core *core; + struct pw_type *t; + struct pw_remote *remote; + struct spa_hook remote_listener; + struct pw_stream *stream; + struct spa_hook stream_listener; + struct spa_video_info_raw pwr_format; + uint32_t seq; + uint32_t node_id; + + bool stream_state; + + pthread_t pwr_thread; + + // wlroots + + struct wl_display *display; + struct wl_list output_list; + struct wl_registry *registry; + struct zwlr_screencopy_manager_v1 *screencopy_manager; + struct wl_shm *shm; + + // main frame callback + struct zwlr_screencopy_frame_v1 *frame_callback; + + // target output + struct wayland_output *target_output; + uint32_t framerate; + bool with_cursor; + + // frame + struct zwlr_screencopy_frame_v1 *wlr_frame; + struct simple_frame simple_frame; + + // frame mutex + pthread_mutex_t lock; + + // if something happens during capture + int err; + bool quit; +}; + +struct wayland_output { + struct wl_list link; + uint32_t id; + struct wl_output *output; + char *make; + char *model; + int width; + int height; + float framerate; +}; + +uint32_t pipewire_from_wl_shm(void *data); +char *strdup(const char *src); + +#endif /* SCREENCAST_COMMON_H */ diff --git a/include/wlr_screencast.h b/include/wlr_screencast.h new file mode 100644 index 0000000..a9f03dc --- /dev/null +++ b/include/wlr_screencast.h @@ -0,0 +1,29 @@ +#ifndef WLR_SCREENCAST_H +#define WLR_SCREENCAST_H + +#include "wlr-screencopy-unstable-v1-client-protocol.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "screencast_common.h" + +#define SC_MANAGER_VERSION 2 + +void wlr_frame_free(struct screencast_context *ctx); +int wlr_screencopy_init(struct screencast_context *ctx); +void wlr_screencopy_uninit(struct screencast_context *ctx); +struct wayland_output *wlr_find_output(struct screencast_context *ctx, + struct wl_output *out, uint32_t id); +void wlr_register_cb(struct screencast_context *ctx); + +#endif diff --git a/include/xdpw.h b/include/xdpw.h index 494f029..267c2c1 100644 --- a/include/xdpw.h +++ b/include/xdpw.h @@ -19,6 +19,7 @@ enum { }; int init_screenshot(sd_bus *bus); +int init_screencast(sd_bus *bus); struct xdpw_request *request_create(sd_bus *bus, const char *object_path); void request_destroy(struct xdpw_request *req); diff --git a/meson.build b/meson.build index 6db0da0..e119149 100644 --- a/meson.build +++ b/meson.build @@ -3,11 +3,12 @@ project( 'c', version: '0.0.0', license: 'MIT', - meson_version: '>=0.43.0', + meson_version: '>=0.46.0', default_options: [ 'c_std=c11', 'warning_level=2', 'werror=true', + 'debug=true', ], ) @@ -19,27 +20,40 @@ add_project_arguments(cc.get_supported_arguments([ '-Wno-unused-parameter', ]), language: 'c') -xdpw_inc = include_directories('include') +inc = include_directories('include') +threads = dependency('threads') +rt = cc.find_library('rt') +pipewire = dependency('libpipewire-0.2') +spa = dependency('libspa-0.1') wayland_client = dependency('wayland-client') wayland_protos = dependency('wayland-protocols', version: '>=1.14') systemd = dependency('libsystemd') -# subdir('protocol') +subdir('protocols') executable( 'xdg-desktop-portal-wlr', files([ - 'main.c', - 'request.c', - 'session.c', - 'screenshot.c', + 'src/core/main.c', + 'src/core/request.c', + 'src/core/session.c', + 'src/screenshot/screenshot.c', + 'src/screencast/screencast.c', + 'src/screencast/screencast_common.c', + 'src/screencast/wlr_screencast.c', + 'src/screencast/pipewire_screencast.c', ]), dependencies: [ wayland_client, + wlr_protos, systemd, + pipewire, + spa, + threads, + rt ], - include_directories: [xdpw_inc], + include_directories: [inc], install: true, ) diff --git a/protocols/meson.build b/protocols/meson.build new file mode 100644 index 0000000..e14a53d --- /dev/null +++ b/protocols/meson.build @@ -0,0 +1,44 @@ +wayland_scanner_dep = dependency('wayland-scanner', required: false, native: true) +if wayland_scanner_dep.found() + wayland_scanner = find_program( + wayland_scanner_dep.get_pkgconfig_variable('wayland_scanner'), + native: true, + ) +else + wayland_scanner = find_program('wayland-scanner', native: true) +endif + +xml='wlr-screencopy-unstable-v1.xml' +wl_protos_src = [] +wl_protos_headers = [] + +wl_protos_src += custom_target( + xml.underscorify() + '_server_c', + input: xml, + output: '@BASENAME@-protocol.c', + command: [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'], + ) + wl_protos_headers += custom_target( + xml.underscorify() + '_server_h', + input: xml, + output: '@BASENAME@-protocol.h', + command: [wayland_scanner, 'server-header', '@INPUT@', '@OUTPUT@'], + ) + +wl_protos_headers += custom_target( + xml.underscorify() + '_client_h', + input: xml, + output: '@BASENAME@-client-protocol.h', + command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@'], + ) + +lib_wl_protos = static_library( + 'wl_protos', + wl_protos_src + wl_protos_headers, + dependencies: wayland_client.partial_dependency(compile_args: true), +) + +wlr_protos = declare_dependency( + link_with: lib_wl_protos, + sources: wl_protos_headers, +) \ No newline at end of file diff --git a/protocols/wlr-screencopy-unstable-v1.xml b/protocols/wlr-screencopy-unstable-v1.xml new file mode 100644 index 0000000..e4c21f8 --- /dev/null +++ b/protocols/wlr-screencopy-unstable-v1.xml @@ -0,0 +1,207 @@ + + + + Copyright © 2018 Simon Ser + Copyright © 2019 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. + + + + + Capture the next frame of an entire output. + + + + + + + + + Capture the next frame of an output's region. + + The region is given in output logical coordinates, see + xdg_output.logical_size. The region will be clipped to the output's + extents. + + + + + + + + + + + + + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + + + + + + + This object represents a single frame. + + When created, a "buffer" event will be sent. The client will then be able + to send a "copy" request. If the capture is successful, the compositor + will send a "flags" followed by a "ready" event. + + If the capture failed, the "failed" event is sent. This can happen anytime + before the "ready" event. + + Once either a "ready" or a "failed" event is received, the client should + destroy the frame. + + + + + Provides information about the frame's buffer. This event is sent once + as soon as the frame is created. + + The client should then create a buffer with the provided attributes, and + send a "copy" request. + + + + + + + + + + Copy the frame to the supplied buffer. The buffer must have a the + correct size, see zwlr_screencopy_frame_v1.buffer. The buffer needs to + have a supported format. + + If the frame is successfully copied, a "flags" and a "ready" events are + sent. Otherwise, a "failed" event is sent. + + + + + + + + + + + + + + + + Provides flags about the frame. This event is sent once before the + "ready" event. + + + + + + + Called as soon as the frame is copied, indicating it is available + for reading. This event includes the time at which presentation happened + at. + + 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]. The seconds part + may have an arbitrary offset at start. + + After receiving this event, the client should destroy the object. + + + + + + + + + This event indicates that the attempted frame copy has failed. + + After receiving this event, the client should destroy the object. + + + + + + Destroys the frame. This request can be sent at any time by the client. + + + + + + + Same as copy, except it waits until there is damage to copy. + + + + + + + This event is sent right before the ready event when copy_with_damage is + requested. It may be generated multiple times for each copy_with_damage + request. + + The arguments describe a box around an area that has changed since the + last copy request that was derived from the current screencopy manager + instance. + + The union of all regions received between the call to copy_with_damage + and a ready event is the total damage since the prior ready event. + + + + + + + + diff --git a/main.c b/src/core/main.c similarity index 97% rename from main.c rename to src/core/main.c index d380b75..178d2b2 100644 --- a/main.c +++ b/src/core/main.c @@ -15,6 +15,7 @@ int main(int argc, char *argv[]) { } init_screenshot(bus); + init_screencast(bus); ret = sd_bus_request_name(bus, service_name, 0); if (ret < 0) { diff --git a/request.c b/src/core/request.c similarity index 80% rename from request.c rename to src/core/request.c index a260096..719a623 100644 --- a/request.c +++ b/src/core/request.c @@ -8,9 +8,25 @@ static const char interface_name[] = "org.freedesktop.impl.portal.Request"; static int method_close(sd_bus_message *msg, void *data, sd_bus_error *ret_error) { + + int ret = 0; // struct xdpw_request *req = data; // TODO printf("Request.Close\n"); + + sd_bus_message *reply = NULL; + ret = sd_bus_message_new_method_return(msg, &reply); + if (ret < 0) { + return ret; + } + + ret = sd_bus_send(NULL, reply, NULL); + if (ret < 0) { + return ret; + } + + sd_bus_message_unref(reply); + return 0; } diff --git a/session.c b/src/core/session.c similarity index 81% rename from session.c rename to src/core/session.c index c7f51ba..8a9b005 100644 --- a/session.c +++ b/src/core/session.c @@ -8,9 +8,25 @@ static const char interface_name[] = "org.freedesktop.impl.portal.Session"; static int method_close(sd_bus_message *msg, void *data, sd_bus_error *ret_error) { + + int ret = 0; // struct xdpw_session *session = data; // TODO printf("Session.Close\n"); + + sd_bus_message *reply = NULL; + ret = sd_bus_message_new_method_return(msg, &reply); + if (ret < 0) { + return ret; + } + + ret = sd_bus_send(NULL, reply, NULL); + if (ret < 0) { + return ret; + } + + sd_bus_message_unref(reply); + return 0; } diff --git a/src/screencast/pipewire_screencast.c b/src/screencast/pipewire_screencast.c new file mode 100644 index 0000000..1c99de7 --- /dev/null +++ b/src/screencast/pipewire_screencast.c @@ -0,0 +1,227 @@ +#include "pipewire_screencast.h" + +static inline void init_type(struct pwr_type *type, struct pw_type *map) { + pw_type_get(map, SPA_TYPE__MediaType, &type->media_type); + pw_type_get(map, SPA_TYPE__MediaSubtype, &type->media_subtype); + pw_type_get(map, SPA_TYPE_FORMAT__Video, &type->format_video); + pw_type_get(map, SPA_TYPE__VideoFormat, &type->video_format); + pw_type_get(map, SPA_TYPE_META__Cursor, &type->meta_cursor); +}; + +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 void pwr_on_event(void *data, uint64_t expirations) { + struct screencast_context *ctx = data; + struct pw_buffer *pw_buf; + struct spa_buffer *spa_buf; + struct spa_meta_header *h; + struct spa_data *d; + + if (!ctx->stream_state) { + wlr_frame_free(ctx); + return; + } + + logger("pw event fired\n"); + + if ((pw_buf = pw_stream_dequeue_buffer(ctx->stream)) == NULL) { + printf("out of buffers\n"); + return; + } + + spa_buf = pw_buf->buffer; + d = spa_buf->datas; + if ((d[0].data) == NULL) + return; + if ((h = spa_buffer_find_meta(spa_buf, ctx->t->meta.Header))) { + h->pts = -1; + h->flags = 0; + h->seq = ctx->seq++; + h->dts_offset = 0; + } + + d[0].type = ctx->t->data.MemPtr; + d[0].maxsize = ctx->simple_frame.size; + // d[0].data = ctx->simple_frame.data; + d[0].mapoffset = 0; + d[0].chunk->size = ctx->simple_frame.size; + d[0].chunk->stride = ctx->simple_frame.stride; + d[0].chunk->offset = 0; + d[0].flags = 0; + d[0].fd = -1; + + writeFrameData(d[0].data, ctx->simple_frame.data, ctx->simple_frame.height, + ctx->simple_frame.stride, ctx->simple_frame.y_invert); + + logger("************** \n"); + logger("pointer: %p\n", d[0].data); + logger("size: %d\n", d[0].maxsize); + logger("stride: %d\n", d[0].chunk->stride); + logger("width: %d\n", ctx->simple_frame.width); + logger("height: %d\n", ctx->simple_frame.height); + logger("y_invert: %d\n", ctx->simple_frame.y_invert); + logger("************** \n"); + + pw_stream_queue_buffer(ctx->stream, pw_buf); + + wlr_frame_free(ctx); +} + +static void pwr_handle_stream_state_changed(void *data, + enum pw_stream_state old, + enum pw_stream_state state, + const char *error) { + struct screencast_context *ctx = data; + ctx->node_id = pw_stream_get_node_id(ctx->stream); + + printf("stream state: \"%s\"\n", pw_stream_state_as_string(state)); + printf("node id: %d\n", ctx->node_id); + + switch (state) { + case PW_STREAM_STATE_PAUSED: + ctx->stream_state = false; + break; + case PW_STREAM_STATE_STREAMING: + ctx->stream_state = true; + break; + default: + ctx->stream_state = false; + break; + } +} + +static void pwr_handle_stream_format_changed(void *data, + const struct spa_pod *format) { + struct screencast_context *ctx = data; + struct pw_stream *stream = ctx->stream; + struct pw_type *t = ctx->t; + 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]; + + if (format == NULL) { + pw_stream_finish_format(stream, 0, NULL, 0); + return; + } + spa_format_video_raw_parse(format, &ctx->pwr_format, &ctx->type.format_video); + + params[0] = spa_pod_builder_object( + &b, t->param.idBuffers, t->param_buffers.Buffers, ":", + t->param_buffers.size, "i", ctx->simple_frame.size, ":", + t->param_buffers.stride, "i", ctx->simple_frame.stride, ":", + t->param_buffers.buffers, "iru", BUFFERS, SPA_POD_PROP_MIN_MAX(1, 32), + ":", t->param_buffers.align, "i", ALIGN); + + params[1] = spa_pod_builder_object(&b, t->param.idMeta, t->param_meta.Meta, + ":", t->param_meta.type, "I", + t->meta.Header, ":", t->param_meta.size, + "i", sizeof(struct spa_meta_header)); + + pw_stream_finish_format(stream, 0, params, 2); +} + +static const struct pw_stream_events pwr_stream_events = { + PW_VERSION_STREAM_EVENTS, + .state_changed = pwr_handle_stream_state_changed, + .format_changed = pwr_handle_stream_format_changed, +}; + +static void pwr_handle_state_changed(void *data, enum pw_remote_state old, + enum pw_remote_state state, + const char *error) { + struct screencast_context *ctx = data; + struct pw_remote *remote = ctx->remote; + + switch (state) { + case PW_REMOTE_STATE_ERROR: + printf("remote error: %s\n", error); + pw_main_loop_quit(ctx->loop); + break; + + case PW_REMOTE_STATE_CONNECTED: { + const struct spa_pod *params[1]; + uint8_t buffer[1024]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + + logger("remote state: \"%s\"\n", pw_remote_state_as_string(state)); + + ctx->stream = pw_stream_new( + remote, "wlr_screeencopy", + pw_properties_new("media.class", "Video/Source", PW_NODE_PROP_MEDIA, + "Video", PW_NODE_PROP_CATEGORY, "Source", + PW_NODE_PROP_ROLE, "Screen", NULL)); + + params[0] = spa_pod_builder_object( + &b, ctx->t->param.idEnumFormat, ctx->t->spa_format, "I", + ctx->type.media_type.video, "I", ctx->type.media_subtype.raw, ":", + ctx->type.format_video.format, "I", pipewire_from_wl_shm(ctx), ":", + ctx->type.format_video.size, "Rru", + &SPA_RECTANGLE(ctx->simple_frame.width, ctx->simple_frame.height), + SPA_POD_PROP_MIN_MAX(&SPA_RECTANGLE(1, 1), &SPA_RECTANGLE(4096, 4096)), + ":", ctx->type.format_video.framerate, "F", + &SPA_FRACTION(ctx->framerate, 1)); + + pw_stream_add_listener(ctx->stream, &ctx->stream_listener, + &pwr_stream_events, ctx); + + pw_stream_connect(ctx->stream, PW_DIRECTION_OUTPUT, NULL, + PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_MAP_BUFFERS, + params, 1); + + break; + } + default: + logger("remote state: \"%s\"\n", pw_remote_state_as_string(state)); + break; + } +} + +static const struct pw_remote_events pwr_remote_events = { + PW_VERSION_REMOTE_EVENTS, + .state_changed = pwr_handle_state_changed, +}; + +void *pwr_start(void *data) { + + struct screencast_context *ctx = data; + + pw_init(NULL, NULL); + + /* create a main loop */ + ctx->loop = pw_main_loop_new(NULL); + ctx->core = pw_core_new(pw_main_loop_get_loop(ctx->loop), NULL); + ctx->t = pw_core_get_type(ctx->core); + ctx->remote = pw_remote_new(ctx->core, NULL, 0); + + init_type(&ctx->type, ctx->t); + + /* make an event to signal frame ready */ + ctx->event = + pw_loop_add_event(pw_main_loop_get_loop(ctx->loop), pwr_on_event, ctx); + + pw_remote_add_listener(ctx->remote, &ctx->remote_listener, &pwr_remote_events, + ctx); + pw_remote_connect(ctx->remote); + + /* run the loop, this will trigger the callbacks */ + pw_main_loop_run(ctx->loop); + + pw_core_destroy(ctx->core); + pw_main_loop_destroy(ctx->loop); + return NULL; +} diff --git a/src/screencast/screencast.c b/src/screencast/screencast.c new file mode 100644 index 0000000..1712a16 --- /dev/null +++ b/src/screencast/screencast.c @@ -0,0 +1,323 @@ +#include "screencast.h" + +static struct screencast_context ctx; + +static const char object_path[] = "/org/freedesktop/portal/desktop"; +static const char interface_name[] = "org.freedesktop.impl.portal.ScreenCast"; + +int setup_outputs(struct screencast_context *ctx) { + + int output_id; + + struct wayland_output *output, *tmp_o; + wl_list_for_each_reverse_safe(output, tmp_o, &ctx->output_list, link) { + printf("Capturable output: %s Model: %s: ID: %i\n", output->make, + output->model, output->id); + output_id = output->id; + } + + output = wlr_find_output(ctx, NULL, output_id); + if (!output) { + printf("Unable to find output with ID %i!\n", output_id); + return 1; + } + + ctx->target_output = output; + ctx->framerate = output->framerate; + ctx->with_cursor = true; + + printf("wl_display fd: %d\n", wl_display_get_fd(ctx->display)); + + return 0; + +} + +void *start_screencast(void *data){ + + struct screencast_context *ctx = data; + + pthread_mutex_init(&ctx->lock, NULL); + + wlr_register_cb(ctx); + + pthread_create(&ctx->pwr_thread, NULL, pwr_start, ctx); + + /* Run capture */ + while (wl_display_dispatch(ctx->display) != -1 && !ctx->err && !ctx->quit); + + pthread_join(ctx->pwr_thread, NULL); + + return NULL; + +} + +static int method_screencast_create_session(sd_bus_message *msg, void *data, + sd_bus_error *ret_error) { + int ret = 0; + + printf("=== CREATE SESSION\n"); + + char *request_handle, *session_handle, *app_id; + ret = sd_bus_message_read(msg, "oos", &request_handle, &session_handle, &app_id); + if (ret < 0) { + return ret; + } + + ret = sd_bus_message_enter_container(msg, 'a', "{sv}"); + if (ret < 0) { + return ret; + } + + printf("request_handle: %s\n", request_handle); + printf("session_handle: %s\n", session_handle); + printf("app_id: %s\n", app_id); + + char* key; + int innerRet = 0; + while((ret = sd_bus_message_enter_container(msg, 'e', "sv")) > 0) { + innerRet = sd_bus_message_read(msg, "s", &key); + if (innerRet < 0) { + return innerRet; + } + + if(strcmp(key, "session_handle_token") == 0) { + char* token; + sd_bus_message_read(msg, "v", "s", &token); + printf("Option token = %s\n", token); + } else { + printf("Unknown option %s\n", key); + sd_bus_message_skip(msg, "v"); + } + + innerRet = sd_bus_message_exit_container(msg); + if (innerRet < 0) { + return innerRet; + } + } + if (ret < 0) { + return ret; + } + + ret = sd_bus_message_exit_container(msg); + if (ret < 0) { + return ret; + } + + // TODO: cleanup this + struct xdpw_request *req = + request_create(sd_bus_message_get_bus(msg), request_handle); + if (req == NULL) { + return -ENOMEM; + } + + // TODO: cleanup this + struct xdpw_session *sess = + session_create(sd_bus_message_get_bus(msg), session_handle); + if (sess == NULL) { + return -ENOMEM; + } + + sd_bus_message *reply = NULL; + ret = sd_bus_message_new_method_return(msg, &reply); + if (ret < 0) { + return ret; + } + ret = sd_bus_message_append(reply, "ua{sv}", PORTAL_RESPONSE_SUCCESS, 0); + if (ret < 0) { + return ret; + } + ret = sd_bus_send(NULL, reply, NULL); + if (ret < 0) { + return ret; + } + + sd_bus_message_unref(reply); + return 0; +} + + +static int method_screencast_select_sources(sd_bus_message *msg, void *data, sd_bus_error *ret_error) { + + struct screencast_context *ctx = data; + + int ret = 0; + + printf("=== SELECT SOURCES\n"); + + setup_outputs(ctx); + + char *request_handle, *session_handle, *app_id; + ret = sd_bus_message_read(msg, "oos", &request_handle, &session_handle, &app_id); + if (ret < 0) { + return ret; + } + sd_bus_message_enter_container(msg, 'a', "{sv}"); + if (ret < 0) { + return ret; + } + + printf("request_handle: %s\n", request_handle); + printf("session_handle: %s\n", session_handle); + printf("app_id: %s\n", app_id); + + char* key; + int innerRet = 0; + while((ret = sd_bus_message_enter_container(msg, 'e', "sv")) > 0) { + innerRet = sd_bus_message_read(msg, "s", &key); + if (innerRet < 0) { + return innerRet; + } + + if(strcmp(key, "multiple") == 0) { + bool multiple; + sd_bus_message_read(msg, "v", "b", &multiple); + printf("Option multiple, val %x\n", multiple); + } else if(strcmp(key, "types") == 0) { + uint32_t mask; + sd_bus_message_read(msg, "v", "u", &mask); + printf("Option types, mask %x\n", mask); + } else { + printf("Unknown option %s\n", key); + sd_bus_message_skip(msg, "v"); + } + + innerRet = sd_bus_message_exit_container(msg); + if (ret < 0) { + return ret; + } + } + if (ret < 0) { + return ret; + } + ret = sd_bus_message_exit_container(msg); + if (ret < 0) { + return ret; + } + + + sd_bus_message *reply = NULL; + ret = sd_bus_message_new_method_return(msg, &reply); + if (ret < 0) { + return ret; + } + ret = sd_bus_message_append(reply, "ua{sv}", PORTAL_RESPONSE_SUCCESS, 0); + if (ret < 0) { + return ret; + } + ret = sd_bus_send(NULL, reply, NULL); + if (ret < 0) { + return ret; + } + + sd_bus_message_unref(reply); + return 0; +} + +static int method_screencast_start(sd_bus_message *msg, void *data, sd_bus_error *ret_error) { + + struct screencast_context *ctx = data; + + int ret = 0; + + printf("=== START\n"); + + pthread_t screencast_thread; + pthread_create(&screencast_thread, NULL, start_screencast, ctx); + + char *request_handle, *session_handle, *app_id, *parent_window; + ret = sd_bus_message_read(msg, "ooss", &request_handle, &session_handle, &app_id, &parent_window); + if (ret < 0) { + return ret; + } + ret = sd_bus_message_enter_container(msg, 'a', "{sv}"); + if (ret < 0) { + return ret; + } + + printf("request_handle: %s\n", request_handle); + printf("session_handle: %s\n", session_handle); + printf("app_id: %s\n", app_id); + printf("parent_window: %s\n", parent_window); + + char* key; + int innerRet = 0; + while((ret = sd_bus_message_enter_container(msg, 'e', "sv")) > 0) { + innerRet = sd_bus_message_read(msg, "s", &key); + if (innerRet < 0) { + return innerRet; + } + + printf("Unknown option %s\n", key); + sd_bus_message_skip(msg, "v"); + + innerRet = sd_bus_message_exit_container(msg); + if (innerRet < 0) { + return innerRet; + } + } + if (ret < 0) { + return ret; + } + ret = sd_bus_message_exit_container(msg); + if (ret < 0) { + return ret; + } + + sd_bus_message *reply = NULL; + ret = sd_bus_message_new_method_return(msg, &reply); + if (ret < 0) { + return ret; + } + + while(ctx->node_id == 0); + + ret = sd_bus_message_append(reply, "ua{sv}", PORTAL_RESPONSE_SUCCESS, 1, + "streams", "a(ua{sv})", 1, + ctx->node_id, 2, + "position", "(ii)", 0, 0, + "size", "(ii)", ctx->simple_frame.width, ctx->simple_frame.height); + if (ret < 0) { + return ret; + } + + ret = sd_bus_send(NULL, reply, NULL); + if (ret < 0) { + return ret; + } + sd_bus_message_unref(reply); + + while (wl_display_dispatch(ctx->display) != -1 && !ctx->err && !ctx->quit); + pthread_join(screencast_thread, NULL); + + return 0; +} + +static const sd_bus_vtable screencast_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("CreateSession", "oosa{sv}", "ua{sv}", method_screencast_create_session, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SelectSources", "oosa{sv}", "ua{sv}", method_screencast_select_sources, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Start", "oossa{sv}", "ua{sv}", method_screencast_start, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_VTABLE_END +}; + +int init_screencast(sd_bus *bus) { + // TODO: cleanup + sd_bus_slot *slot = NULL; + + //struct screencast_context ctx = (struct screencast_context){0}; + ctx.simple_frame = (struct simple_frame){0}; + ctx.simple_frame.damage = &(struct damage){0}; + + int err; + err = wlr_screencopy_init(&ctx); + if (err) { + goto end; + } + + return sd_bus_add_object_vtable(bus, &slot, object_path, interface_name, + screencast_vtable, &ctx); + + end: + wlr_screencopy_uninit(&ctx); + return err; +} diff --git a/src/screencast/screencast_common.c b/src/screencast/screencast_common.c new file mode 100644 index 0000000..969c459 --- /dev/null +++ b/src/screencast/screencast_common.c @@ -0,0 +1,35 @@ +#include "screencast_common.h" + +char *strdup(const char *src) { + char *dst = malloc(strlen(src) + 1); // Space for length plus nul + if (dst == NULL) + return NULL; // No memory + strcpy(dst, src); // Copy the characters + return dst; // Return the new string +} + +uint32_t pipewire_from_wl_shm(void *data) { + struct screencast_context *ctx = data; + switch (ctx->simple_frame.format) { + case WL_SHM_FORMAT_ARGB8888: + return ctx->type.video_format.BGRA; + case WL_SHM_FORMAT_XRGB8888: + return ctx->type.video_format.BGRx; + case WL_SHM_FORMAT_RGBA8888: + return ctx->type.video_format.ABGR; + case WL_SHM_FORMAT_RGBX8888: + return ctx->type.video_format.xBGR; + case WL_SHM_FORMAT_ABGR8888: + return ctx->type.video_format.RGBA; + case WL_SHM_FORMAT_XBGR8888: + return ctx->type.video_format.RGBx; + case WL_SHM_FORMAT_BGRA8888: + return ctx->type.video_format.ARGB; + case WL_SHM_FORMAT_BGRX8888: + return ctx->type.video_format.xRGB; + case WL_SHM_FORMAT_NV12: + return ctx->type.video_format.NV12; + default: + exit(EXIT_FAILURE); + } +} diff --git a/src/screencast/wlr_screencast.c b/src/screencast/wlr_screencast.c new file mode 100644 index 0000000..41479a6 --- /dev/null +++ b/src/screencast/wlr_screencast.c @@ -0,0 +1,278 @@ +#include "wlr_screencast.h" + +void wlr_frame_free(struct screencast_context *ctx) { + + zwlr_screencopy_frame_v1_destroy(ctx->wlr_frame); + munmap(ctx->simple_frame.data, ctx->simple_frame.size); + wl_buffer_destroy(ctx->simple_frame.buffer); + logger("wlr frame destroyed\n"); + pthread_mutex_unlock(&ctx->lock); + +} + +static struct wl_buffer *create_shm_buffer(struct screencast_context *ctx, + enum wl_shm_format fmt, int width, + int height, int stride, + void **data_out) { + int size = stride * height; + + const char shm_name[] = "/wlroots-screencopy"; + int fd = shm_open(shm_name, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); + if (fd < 0) { + fprintf(stderr, "shm_open failed\n"); + return NULL; + } + shm_unlink(shm_name); + + int ret; + while ((ret = ftruncate(fd, size)) == EINTR) { + // No-op + } + if (ret < 0) { + close(fd); + fprintf(stderr, "ftruncate failed\n"); + return NULL; + } + + void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + fprintf(stderr, "mmap failed: %m\n"); + close(fd); + return NULL; + } + + struct wl_shm_pool *pool = wl_shm_create_pool(ctx->shm, fd, size); + close(fd); + struct wl_buffer *buffer = + wl_shm_pool_create_buffer(pool, 0, width, height, stride, fmt); + wl_shm_pool_destroy(pool); + + *data_out = data; + return buffer; +} + +static void wlr_frame_buffer(void *data, struct zwlr_screencopy_frame_v1 *frame, + uint32_t format, uint32_t width, uint32_t height, + uint32_t stride) { + struct screencast_context *ctx = data; + + pthread_mutex_lock(&ctx->lock); + + logger("wlr buffer event handler\n"); + ctx->wlr_frame = frame; + ctx->simple_frame.width = width; + ctx->simple_frame.height = height; + ctx->simple_frame.stride = stride; + ctx->simple_frame.size = stride * height; + ctx->simple_frame.format = format; + ctx->simple_frame.buffer = create_shm_buffer(ctx, format, width, height, + stride, &ctx->simple_frame.data); + if (ctx->simple_frame.buffer == NULL) { + fprintf(stderr, "failed to create buffer\n"); + exit(EXIT_FAILURE); + } + + // zwlr_screencopy_frame_v1_copy(frame, ctx->simple_frame.buffer); + zwlr_screencopy_frame_v1_copy_with_damage(frame, ctx->simple_frame.buffer); + pthread_mutex_unlock(&ctx->lock); +} + +static void wlr_frame_flags(void *data, struct zwlr_screencopy_frame_v1 *frame, + uint32_t flags) { + struct screencast_context *ctx = data; + + pthread_mutex_lock(&ctx->lock); + + logger("wlr flags event handler\n"); + ctx->simple_frame.y_invert = flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT; + + pthread_mutex_unlock(&ctx->lock); +} + +static void wlr_frame_ready(void *data, struct zwlr_screencopy_frame_v1 *frame, + uint32_t tv_sec_hi, uint32_t tv_sec_lo, + uint32_t tv_nsec) { + struct screencast_context *ctx = data; + + pthread_mutex_lock(&ctx->lock); + + logger("wlr ready event handler\n"); + + ctx->simple_frame.tv_sec = ((((uint64_t)tv_sec_hi) << 32) | tv_sec_lo); + ctx->simple_frame.tv_nsec = tv_nsec; + + if (!ctx->quit && !ctx->err) { + pw_loop_signal_event(pw_main_loop_get_loop(ctx->loop), ctx->event); + // sleep(1); + wlr_register_cb(ctx); + } +} + +static void wlr_frame_failed(void *data, + struct zwlr_screencopy_frame_v1 *frame) { + struct screencast_context *ctx = data; + + logger("wlr failed event handler\n"); + + wlr_frame_free(ctx); + ctx->err = true; +} + +static void wlr_frame_damage(void *data, struct zwlr_screencopy_frame_v1 *frame, + uint32_t x, uint32_t y, uint32_t width, + uint32_t height) { + struct screencast_context *ctx = data; + + ctx->simple_frame.damage->x = x; + ctx->simple_frame.damage->y = y; + ctx->simple_frame.damage->width = width; + ctx->simple_frame.damage->height = height; +} + +static const struct zwlr_screencopy_frame_v1_listener wlr_frame_listener = { + .buffer = wlr_frame_buffer, + .flags = wlr_frame_flags, + .ready = wlr_frame_ready, + .failed = wlr_frame_failed, + .damage = wlr_frame_damage, +}; + +void wlr_register_cb(struct screencast_context *ctx) { + ctx->frame_callback = zwlr_screencopy_manager_v1_capture_output( + ctx->screencopy_manager, ctx->with_cursor, ctx->target_output->output); + + zwlr_screencopy_frame_v1_add_listener(ctx->frame_callback, + &wlr_frame_listener, ctx); + logger("wlr callbacks registered\n"); +} + +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) { + struct wayland_output *output = data; + output->make = strdup(make); + output->model = strdup(model); +} + +static void wlr_output_handle_mode(void *data, struct wl_output *wl_output, + uint32_t flags, int32_t width, + int32_t height, int32_t refresh) { + if (flags & WL_OUTPUT_MODE_CURRENT) { + struct wayland_output *output = data; + output->framerate = (float)refresh * 1000; + } +} + +static void wlr_output_handle_done(void *data, struct wl_output *wl_output) { + /* Nothing to do */ +} + +static void wlr_output_handle_scale(void *data, struct wl_output *wl_output, + int32_t factor) { + /* Nothing to do */ +} + +static const struct wl_output_listener wlr_output_listener = { + .geometry = wlr_output_handle_geometry, + .mode = wlr_output_handle_mode, + .done = wlr_output_handle_done, + .scale = wlr_output_handle_scale, +}; + +struct wayland_output *wlr_find_output(struct screencast_context *ctx, + struct wl_output *out, uint32_t id) { + struct wayland_output *output, *tmp; + wl_list_for_each_safe(output, tmp, &ctx->output_list, link) { + if ((output->output == out) || (output->id == id)) { + return output; + } + } + return NULL; +} + +static void wlr_remove_output(struct wayland_output *out) { + wl_list_remove(&out->link); +} + +static void wlr_registry_handle_add(void *data, struct wl_registry *reg, + uint32_t id, const char *interface, + uint32_t ver) { + struct screencast_context *ctx = data; + + if (!strcmp(interface, wl_output_interface.name)) { + struct wayland_output *output = malloc(sizeof(*output)); + + output->id = id; + output->output = wl_registry_bind(reg, id, &wl_output_interface, 1); + + wl_output_add_listener(output->output, &wlr_output_listener, output); + wl_list_insert(&ctx->output_list, &output->link); + } + + if (!strcmp(interface, zwlr_screencopy_manager_v1_interface.name)) { + ctx->screencopy_manager = wl_registry_bind( + reg, id, &zwlr_screencopy_manager_v1_interface, SC_MANAGER_VERSION); + } + + if (strcmp(interface, wl_shm_interface.name) == 0) { + ctx->shm = wl_registry_bind(reg, id, &wl_shm_interface, 1); + } +} + +static void wlr_registry_handle_remove(void *data, struct wl_registry *reg, + uint32_t id) { + wlr_remove_output( + wlr_find_output((struct screencast_context *)data, NULL, id)); +} + +static const struct wl_registry_listener wlr_registry_listener = { + .global = wlr_registry_handle_add, + .global_remove = wlr_registry_handle_remove, +}; + +int wlr_screencopy_init(struct screencast_context *ctx) { + // connect to wayland display WAYLAND_DISPLAY or 'wayland-0' if not set + ctx->display = wl_display_connect(NULL); + if (!ctx->display) { + printf("Failed to connect to display!\n"); + return -1; + } + + // retrieve list of outputs + wl_list_init(&ctx->output_list); + + // retrieve registry + ctx->registry = wl_display_get_registry(ctx->display); + wl_registry_add_listener(ctx->registry, &wlr_registry_listener, ctx); + + wl_display_roundtrip(ctx->display); + wl_display_dispatch(ctx->display); + + // make sure our wlroots supports screencopy protocol + if (!ctx->shm) { + printf("Compositor doesn't support %s!\n", "wl_shm"); + return -1; + } + + // make sure our wlroots supports screencopy protocol + if (!ctx->screencopy_manager) { + printf("Compositor doesn't support %s!\n", + zwlr_screencopy_manager_v1_interface.name); + return -1; + } + + return 0; +} + +void wlr_screencopy_uninit(struct screencast_context *ctx) { + struct wayland_output *output, *tmp_o; + wl_list_for_each_safe(output, tmp_o, &ctx->output_list, link) { + wl_list_remove(&output->link); + } + + if (ctx->screencopy_manager) { + zwlr_screencopy_manager_v1_destroy(ctx->screencopy_manager); + } +} diff --git a/screenshot.c b/src/screenshot/screenshot.c similarity index 100% rename from screenshot.c rename to src/screenshot/screenshot.c