Add thread for wlr screensharing

This commit is contained in:
Dan Shick 2020-01-24 17:31:01 -05:00 committed by Simon Ser
parent 3058987873
commit 2a31d2d922
17 changed files with 1345 additions and 8 deletions

3
.gitignore vendored
View file

@ -50,3 +50,6 @@ modules.order
Module.symvers
Mkfile.old
dkms.conf
# build folder
build/

View file

@ -0,0 +1,18 @@
#ifndef PIPEWIRE_SCREENCAST_H
#define PIPEWIRE_SCREENCAST_H
#include <stdio.h>
#include <pipewire/pipewire.h>
#include <spa/param/format-utils.h>
#include <spa/param/props.h>
#include <spa/param/video/format-utils.h>
#include <spa/support/type-map.h>
#include "wlr_screencast.h"
#include "screencast_common.h"
#define BUFFERS 1
#define ALIGN 16
void *pwr_start(void *data);
#endif

18
include/screencast.h Normal file
View file

@ -0,0 +1,18 @@
#ifndef SCREENCAST_H
#define SCREENCAST_H
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <pthread.h>
#include "pipewire_screencast.h"
#include "screencast_common.h"
#include "wlr_screencast.h"
#include "xdpw.h"
#endif

107
include/screencast_common.h Normal file
View file

@ -0,0 +1,107 @@
#ifndef SCREENCAST_COMMON_H
#define SCREENCAST_COMMON_H
#include <pthread.h>
#include <pipewire/pipewire.h>
#include <spa/param/video/format-utils.h>
#include <libdrm/drm_fourcc.h>
#include <wayland-client-protocol.h>
// 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 */

29
include/wlr_screencast.h Normal file
View file

@ -0,0 +1,29 @@
#ifndef WLR_SCREENCAST_H
#define WLR_SCREENCAST_H
#include "wlr-screencopy-unstable-v1-client-protocol.h"
#include <fcntl.h>
#include <limits.h>
#include <png.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <wayland-client-protocol.h>
#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

View file

@ -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);

View file

@ -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,
)

44
protocols/meson.build Normal file
View file

@ -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,
)

View file

@ -0,0 +1,207 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="wlr_screencopy_unstable_v1">
<copyright>
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.
</copyright>
<description summary="screen content capturing on client buffers">
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.
</description>
<interface name="zwlr_screencopy_manager_v1" version="2">
<description summary="manager to inform clients and begin capturing">
This object is a manager which offers requests to start capturing from a
source.
</description>
<request name="capture_output">
<description summary="capture an output">
Capture the next frame of an entire output.
</description>
<arg name="frame" type="new_id" interface="zwlr_screencopy_frame_v1"/>
<arg name="overlay_cursor" type="int"
summary="composite cursor onto the frame"/>
<arg name="output" type="object" interface="wl_output"/>
</request>
<request name="capture_output_region">
<description summary="capture an output's region">
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.
</description>
<arg name="frame" type="new_id" interface="zwlr_screencopy_frame_v1"/>
<arg name="overlay_cursor" type="int"
summary="composite cursor onto the frame"/>
<arg name="output" type="object" interface="wl_output"/>
<arg name="x" type="int"/>
<arg name="y" type="int"/>
<arg name="width" type="int"/>
<arg name="height" type="int"/>
</request>
<request name="destroy" type="destructor">
<description summary="destroy the manager">
All objects created by the manager will still remain valid, until their
appropriate destroy request has been called.
</description>
</request>
</interface>
<interface name="zwlr_screencopy_frame_v1" version="2">
<description summary="a frame ready for copy">
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.
</description>
<event name="buffer">
<description summary="buffer information">
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.
</description>
<arg name="format" type="uint" summary="buffer format"/>
<arg name="width" type="uint" summary="buffer width"/>
<arg name="height" type="uint" summary="buffer height"/>
<arg name="stride" type="uint" summary="buffer stride"/>
</event>
<request name="copy">
<description summary="copy the frame">
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.
</description>
<arg name="buffer" type="object" interface="wl_buffer"/>
</request>
<enum name="error">
<entry name="already_used" value="0"
summary="the object has already been used to copy a wl_buffer"/>
<entry name="invalid_buffer" value="1"
summary="buffer attributes are invalid"/>
</enum>
<enum name="flags" bitfield="true">
<entry name="y_invert" value="1" summary="contents are y-inverted"/>
</enum>
<event name="flags">
<description summary="frame flags">
Provides flags about the frame. This event is sent once before the
"ready" event.
</description>
<arg name="flags" type="uint" enum="flags" summary="frame flags"/>
</event>
<event name="ready">
<description summary="indicates frame is available for reading">
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.
</description>
<arg name="tv_sec_hi" type="uint"
summary="high 32 bits of the seconds part of the timestamp"/>
<arg name="tv_sec_lo" type="uint"
summary="low 32 bits of the seconds part of the timestamp"/>
<arg name="tv_nsec" type="uint"
summary="nanoseconds part of the timestamp"/>
</event>
<event name="failed">
<description summary="frame copy failed">
This event indicates that the attempted frame copy has failed.
After receiving this event, the client should destroy the object.
</description>
</event>
<request name="destroy" type="destructor">
<description summary="delete this object, used or not">
Destroys the frame. This request can be sent at any time by the client.
</description>
</request>
<!-- Version 2 additions -->
<request name="copy_with_damage" since="2">
<description summary="copy the frame when it's damaged">
Same as copy, except it waits until there is damage to copy.
</description>
<arg name="buffer" type="object" interface="wl_buffer"/>
</request>
<event name="damage" since="2">
<description summary="carries the coordinates of the damaged region">
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.
</description>
<arg name="x" type="uint" summary="damaged x coordinates"/>
<arg name="y" type="uint" summary="damaged y coordinates"/>
<arg name="width" type="uint" summary="current width"/>
<arg name="height" type="uint" summary="current height"/>
</event>
</interface>
</protocol>

View file

@ -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) {

View file

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

View file

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

View file

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

323
src/screencast/screencast.c Normal file
View file

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

View file

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

View file

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