Screencast session support (#22)

* Initial session support WIP
Remove libdrm dependency

Remove display from context, add dbus properties

Use random names for shm and pw_stream, init the stream only for new cast instances

Separate cast initialized flag from refcount, cleanup names and comments

* Refactor and stability improvements

Properly report xdp screencast implementation version
This commit is contained in:
danshick 2020-04-16 04:21:55 -04:00 committed by GitHub
parent ea98281d0a
commit 55f873dac4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 544 additions and 291 deletions

View file

@ -6,7 +6,6 @@ packages:
- wayland - wayland
- wayland-protocols - wayland-protocols
- pipewire - pipewire
- libdrm
sources: sources:
- https://github.com/emersion/xdg-desktop-portal-wlr - https://github.com/emersion/xdg-desktop-portal-wlr
tasks: tasks:

View file

@ -1,11 +1,7 @@
#ifndef LOGGER_H #ifndef LOGGER_H
#define LOGGER_H #define LOGGER_H
#include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
enum LOGLEVEL { QUIET, ERROR, WARN, INFO, DEBUG, TRACE }; enum LOGLEVEL { QUIET, ERROR, WARN, INFO, DEBUG, TRACE };

View file

@ -1,20 +1,13 @@
#ifndef PIPEWIRE_SCREENCAST_H #ifndef PIPEWIRE_SCREENCAST_H
#define PIPEWIRE_SCREENCAST_H #define PIPEWIRE_SCREENCAST_H
#include <stdio.h>
#include <pipewire/pipewire.h>
#include <spa/utils/result.h>
#include <spa/param/props.h>
#include <spa/param/format-utils.h>
#include <spa/param/video/format-utils.h>
#include "wlr_screencast.h"
#include "screencast_common.h" #include "screencast_common.h"
#include "xdpw.h"
#define BUFFERS 1 #define BUFFERS 1
#define ALIGN 16 #define ALIGN 16
void *pwr_start(struct xdpw_state *state); void xdpw_pwr_stream_init(struct xdpw_screencast_instance *cast);
int xdpw_pwr_core_connect(struct xdpw_state *state);
void xdpw_pwr_stream_destroy(struct xdpw_screencast_instance *cast);
#endif #endif

View file

@ -1,17 +1,8 @@
#ifndef SCREENCAST_H #ifndef SCREENCAST_H
#define 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 "pipewire_screencast.h"
#include "screencast_common.h" #include "screencast_common.h"
#include "wlr_screencast.h"
#include "xdpw.h" void xdpw_screencast_instance_destroy(struct xdpw_screencast_instance *cast);
#endif #endif

View file

@ -1,21 +1,33 @@
#ifndef SCREENCAST_COMMON_H #ifndef SCREENCAST_COMMON_H
#define SCREENCAST_COMMON_H #define SCREENCAST_COMMON_H
#include <string.h>
#include <pipewire/pipewire.h> #include <pipewire/pipewire.h>
#include <spa/param/video/format-utils.h> #include <spa/param/video/format-utils.h>
#include <libdrm/drm_fourcc.h>
#include <wayland-client-protocol.h> #include <wayland-client-protocol.h>
#include "logger.h"
struct damage { // this seems to be right based on
// https://github.com/flatpak/xdg-desktop-portal/blob/309a1fc0cf2fb32cceb91dbc666d20cf0a3202c2/src/screen-cast.c#L955
#define XDP_CAST_PROTO_VER 2
enum cursor_modes {
HIDDEN = 1,
EMBEDDED = 2,
METADATA = 4,
};
enum source_types {
MONITOR = 1,
WINDOW = 2,
};
struct xdpw_frame_damage {
uint32_t x; uint32_t x;
uint32_t y; uint32_t y;
uint32_t width; uint32_t width;
uint32_t height; uint32_t height;
}; };
struct simple_frame { struct xdpw_frame {
uint32_t width; uint32_t width;
uint32_t height; uint32_t height;
uint32_t size; uint32_t size;
@ -24,53 +36,65 @@ struct simple_frame {
uint64_t tv_sec; uint64_t tv_sec;
uint32_t tv_nsec; uint32_t tv_nsec;
enum wl_shm_format format; enum wl_shm_format format;
struct damage *damage; struct xdpw_frame_damage damage;
struct wl_buffer *buffer; struct wl_buffer *buffer;
void *data; void *data;
}; };
struct screencast_context { struct xdpw_screencast_context {
// xdpw
struct xdpw_state *state;
// pipewire // pipewire
struct pw_context *pwr_context; struct pw_context *pwr_context;
struct pw_core *core; struct pw_core *core;
struct spa_source *event;
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;
// wlroots // wlroots
struct wl_display *display;
struct wl_list output_list; struct wl_list output_list;
struct wl_registry *registry; struct wl_registry *registry;
struct zwlr_screencopy_manager_v1 *screencopy_manager; struct zwlr_screencopy_manager_v1 *screencopy_manager;
struct zxdg_output_manager_v1* xdg_output_manager; struct zxdg_output_manager_v1* xdg_output_manager;
struct wl_shm *shm; 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;
// cli options // cli options
const char *output_name; const char *output_name;
const char *forced_pixelformat; const char *forced_pixelformat;
// if something happens during capture // sessions
struct wl_list screencast_instances;
};
struct xdpw_screencast_instance {
// list
struct wl_list link;
// xdpw
uint32_t refcount;
struct xdpw_screencast_context *ctx;
bool initialized;
// pipewire
struct spa_source *event;
struct pw_stream *stream;
struct spa_hook stream_listener;
struct spa_video_info_raw pwr_format;
uint32_t seq;
uint32_t node_id;
bool pwr_stream_state;
// wlroots
struct zwlr_screencopy_frame_v1 *frame_callback;
struct xdpw_wlr_output *target_output;
uint32_t framerate;
struct zwlr_screencopy_frame_v1 *wlr_frame;
struct xdpw_frame simple_frame;
bool with_cursor;
int err; int err;
bool quit; bool quit;
}; };
struct wayland_output { struct xdpw_wlr_output {
struct wl_list link; struct wl_list link;
uint32_t id; uint32_t id;
struct wl_output *output; struct wl_output *output;
@ -83,6 +107,7 @@ struct wayland_output {
float framerate; float framerate;
}; };
uint32_t pipewire_from_wl_shm(void *data); void randname(char *buf);
uint32_t xdpw_format_pw_from_wl_shm(void *data);
#endif /* SCREENCAST_COMMON_H */ #endif /* SCREENCAST_COMMON_H */

View file

@ -1,37 +1,22 @@
#ifndef WLR_SCREENCAST_H #ifndef WLR_SCREENCAST_H
#define WLR_SCREENCAST_H #define WLR_SCREENCAST_H
#include "wlr-screencopy-unstable-v1-client-protocol.h"
#include "xdg-output-unstable-v1-client-protocol.h"
#include <fcntl.h>
#include <limits.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 "pipewire_screencast.h"
#include "screencast_common.h" #include "screencast_common.h"
#define SC_MANAGER_VERSION 2 #define SC_MANAGER_VERSION 2
struct xdpw_state; struct xdpw_state;
void wlr_frame_free(struct xdpw_state *state); int xdpw_wlr_screencopy_init(struct xdpw_state *state);
int wlr_screencopy_init(struct xdpw_state *state); void xdpw_wlr_screencopy_finish(struct xdpw_screencast_context *ctx);
void wlr_screencopy_uninit(struct screencast_context *ctx);
struct wayland_output *wlr_output_find_by_name(struct wl_list *output_list, struct xdpw_wlr_output *xdpw_wlr_output_find_by_name(struct wl_list *output_list,
const char *name); const char *name);
struct wayland_output *wlr_output_find(struct screencast_context *ctx, struct xdpw_wlr_output *xdpw_wlr_output_first(struct wl_list *output_list);
struct xdpw_wlr_output *xdpw_wlr_output_find(struct xdpw_screencast_context *ctx,
struct wl_output *out, uint32_t id); struct wl_output *out, uint32_t id);
struct wayland_output *wlr_output_first(struct wl_list *output_list);
void wlr_register_cb(struct xdpw_state *state); void xdpw_wlr_frame_free(struct xdpw_screencast_instance *cast);
void xdpw_wlr_register_cb(struct xdpw_screencast_instance *cast);
#endif #endif

View file

@ -7,15 +7,18 @@
#elif HAVE_ELOGIND #elif HAVE_ELOGIND
#include <elogind/sd-bus.h> #include <elogind/sd-bus.h>
#endif #endif
#include "logger.h"
#include "screencast.h" #include "screencast_common.h"
struct xdpw_state { struct xdpw_state {
struct wl_list xdpw_sessions;
sd_bus *bus; sd_bus *bus;
struct wl_display *wl_display; struct wl_display *wl_display;
struct pw_loop *pw_loop; struct pw_loop *pw_loop;
struct xdpw_screencast_context screencast;
struct screencast_context screencast; uint32_t screencast_source_types; // bitfield of enum source_types
uint32_t screencast_cursor_modes; // bitfield of enum cursor_modes
uint32_t screencast_version;
}; };
struct xdpw_request { struct xdpw_request {
@ -23,7 +26,10 @@ struct xdpw_request {
}; };
struct xdpw_session { struct xdpw_session {
struct wl_list link;
sd_bus_slot *slot; sd_bus_slot *slot;
char *session_handle;
struct xdpw_screencast_instance *screencast_instance;
}; };
enum { enum {
@ -32,14 +38,14 @@ enum {
PORTAL_RESPONSE_ENDED = 2 PORTAL_RESPONSE_ENDED = 2
}; };
int init_screenshot(struct xdpw_state *state); int xdpw_screenshot_init(struct xdpw_state *state);
int init_screencast(struct xdpw_state *state, const char *output_name, int xdpw_screencast_init(struct xdpw_state *state, const char *output_name,
const char *forced_pixelformat); const char *forced_pixelformat);
struct xdpw_request *request_create(sd_bus *bus, const char *object_path); struct xdpw_request *xdpw_request_create(sd_bus *bus, const char *object_path);
void request_destroy(struct xdpw_request *req); void xdpw_request_destroy(struct xdpw_request *req);
struct xdpw_session *session_create(sd_bus *bus, const char *object_path); struct xdpw_session *xdpw_session_create(struct xdpw_state *state, sd_bus *bus, char *object_path);
void session_destroy(struct xdpw_session *req); void xdpw_session_destroy(struct xdpw_session *req);
#endif #endif

View file

@ -25,7 +25,6 @@ rt = cc.find_library('rt')
pipewire = dependency('libpipewire-0.3', version: '>= 0.2.9') pipewire = dependency('libpipewire-0.3', version: '>= 0.2.9')
wayland_client = dependency('wayland-client') wayland_client = dependency('wayland-client')
wayland_protos = dependency('wayland-protocols', version: '>=1.14') wayland_protos = dependency('wayland-protocols', version: '>=1.14')
drm = dependency('libdrm').partial_dependency(includes: true)
logind = dependency('libsystemd', required: false) logind = dependency('libsystemd', required: false)
if logind.found() if logind.found()
@ -56,7 +55,6 @@ executable(
logind, logind,
pipewire, pipewire,
rt, rt,
drm
], ],
include_directories: [inc], include_directories: [inc],
install: true, install: true,

View file

@ -1,5 +1,10 @@
#include "logger.h" #include "logger.h"
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
static int NUM_LEVELS = 6; static int NUM_LEVELS = 6;
static const char *loglevels[] = { static const char *loglevels[] = {

View file

@ -6,6 +6,7 @@
#include <pipewire/pipewire.h> #include <pipewire/pipewire.h>
#include <spa/utils/result.h> #include <spa/utils/result.h>
#include "xdpw.h" #include "xdpw.h"
#include "logger.h"
enum event_loop_fd { enum event_loop_fd {
EVENT_LOOP_DBUS, EVENT_LOOP_DBUS,
@ -77,12 +78,14 @@ int main(int argc, char *argv[]) {
logprint(ERROR, "dbus: failed to connect to user bus: %s", strerror(-ret)); logprint(ERROR, "dbus: failed to connect to user bus: %s", strerror(-ret));
goto error; goto error;
} }
logprint(TRACE, "dbus: connected");
struct wl_display *wl_display = wl_display_connect(NULL); struct wl_display *wl_display = wl_display_connect(NULL);
if (!wl_display) { if (!wl_display) {
logprint(ERROR, "wayland: failed to connect to display"); logprint(ERROR, "wayland: failed to connect to display");
goto error; goto error;
} }
logprint(TRACE, "wlroots: wl_display connected");
pw_init(NULL, NULL); pw_init(NULL, NULL);
struct pw_loop *pw_loop = pw_loop_new(NULL); struct pw_loop *pw_loop = pw_loop_new(NULL);
@ -90,15 +93,25 @@ int main(int argc, char *argv[]) {
logprint(ERROR, "pipewire: failed to create loop"); logprint(ERROR, "pipewire: failed to create loop");
goto error; goto error;
} }
logprint(TRACE, "pipewire: pw_loop created");
struct xdpw_state state = { struct xdpw_state state = {
.bus = bus, .bus = bus,
.wl_display = wl_display, .wl_display = wl_display,
.pw_loop = pw_loop, .pw_loop = pw_loop,
.screencast_source_types = MONITOR,
.screencast_cursor_modes = HIDDEN | EMBEDDED,
.screencast_version = XDP_CAST_PROTO_VER,
}; };
init_screenshot(&state); wl_list_init(&state.xdpw_sessions);
init_screencast(&state, output_name, forced_pixelformat);
xdpw_screenshot_init(&state);
ret = xdpw_screencast_init(&state, output_name, forced_pixelformat);
if (ret < 0) {
logprint(ERROR, "xdpw: failed to initialize screencast");
goto error;
}
ret = sd_bus_request_name(bus, service_name, 0); ret = sd_bus_request_name(bus, service_name, 0);
if (ret < 0) { if (ret < 0) {
@ -173,5 +186,6 @@ error:
sd_bus_unref(bus); sd_bus_unref(bus);
pw_loop_leave(state.pw_loop); pw_loop_leave(state.pw_loop);
pw_loop_destroy(state.pw_loop); pw_loop_destroy(state.pw_loop);
wl_display_disconnect(state.wl_display);
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View file

@ -3,15 +3,14 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "xdpw.h" #include "xdpw.h"
#include "logger.h"
static const char interface_name[] = "org.freedesktop.impl.portal.Request"; static const char interface_name[] = "org.freedesktop.impl.portal.Request";
static int method_close(sd_bus_message *msg, void *data, static int method_close(sd_bus_message *msg, void *data,
sd_bus_error *ret_error) { sd_bus_error *ret_error) {
struct xdpw_request *req = data;
int ret = 0; int ret = 0;
// struct xdpw_request *req = data;
// TODO
logprint(INFO, "dbus: request closed"); logprint(INFO, "dbus: request closed");
sd_bus_message *reply = NULL; sd_bus_message *reply = NULL;
@ -27,6 +26,8 @@ static int method_close(sd_bus_message *msg, void *data,
sd_bus_message_unref(reply); sd_bus_message_unref(reply);
xdpw_request_destroy(req);
return 0; return 0;
} }
@ -36,7 +37,7 @@ static const sd_bus_vtable request_vtable[] = {
SD_BUS_VTABLE_END SD_BUS_VTABLE_END
}; };
struct xdpw_request *request_create(sd_bus *bus, const char *object_path) { struct xdpw_request *xdpw_request_create(sd_bus *bus, const char *object_path) {
struct xdpw_request *req = calloc(1, sizeof(struct xdpw_request)); struct xdpw_request *req = calloc(1, sizeof(struct xdpw_request));
if (sd_bus_add_object_vtable(bus, &req->slot, object_path, interface_name, if (sd_bus_add_object_vtable(bus, &req->slot, object_path, interface_name,
@ -50,7 +51,7 @@ struct xdpw_request *request_create(sd_bus *bus, const char *object_path) {
return req; return req;
} }
void request_destroy(struct xdpw_request *req) { void xdpw_request_destroy(struct xdpw_request *req) {
if (req == NULL) { if (req == NULL) {
return; return;
} }

View file

@ -2,16 +2,17 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <assert.h>
#include "xdpw.h" #include "xdpw.h"
#include "screencast.h"
#include "logger.h"
static const char interface_name[] = "org.freedesktop.impl.portal.Session"; static const char interface_name[] = "org.freedesktop.impl.portal.Session";
static int method_close(sd_bus_message *msg, void *data, static int method_close(sd_bus_message *msg, void *data,
sd_bus_error *ret_error) { sd_bus_error *ret_error) {
int ret = 0; int ret = 0;
// struct xdpw_session *session = data; struct xdpw_session *sess = data;
// TODO
logprint(INFO, "dbus: session closed"); logprint(INFO, "dbus: session closed");
sd_bus_message *reply = NULL; sd_bus_message *reply = NULL;
@ -27,6 +28,8 @@ static int method_close(sd_bus_message *msg, void *data,
sd_bus_message_unref(reply); sd_bus_message_unref(reply);
xdpw_session_destroy(sess);
return 0; return 0;
} }
@ -36,24 +39,40 @@ static const sd_bus_vtable session_vtable[] = {
SD_BUS_VTABLE_END SD_BUS_VTABLE_END
}; };
struct xdpw_session *session_create(sd_bus *bus, const char *object_path) { struct xdpw_session *xdpw_session_create(struct xdpw_state *state, sd_bus *bus, char *object_path) {
struct xdpw_session *req = calloc(1, sizeof(struct xdpw_session)); struct xdpw_session *sess = calloc(1, sizeof(struct xdpw_session));
if (sd_bus_add_object_vtable(bus, &req->slot, object_path, interface_name, sess->session_handle = object_path;
session_vtable, NULL) < 0) {
free(req); if (sd_bus_add_object_vtable(bus, &sess->slot, object_path, interface_name,
session_vtable, sess) < 0) {
free(sess);
logprint(ERROR, "dbus: sd_bus_add_object_vtable failed: %s", logprint(ERROR, "dbus: sd_bus_add_object_vtable failed: %s",
strerror(-errno)); strerror(-errno));
return NULL; return NULL;
} }
return req; wl_list_insert(&state->xdpw_sessions, &sess->link);
return sess;
} }
void session_destroy(struct xdpw_session *req) { void xdpw_session_destroy(struct xdpw_session *sess) {
if (req == NULL) { logprint(TRACE, "dbus: destroying session");
if (!sess) {
return; return;
} }
sd_bus_slot_unref(req->slot); struct xdpw_screencast_instance *cast = sess->screencast_instance;
free(req); if (cast) {
assert(cast->refcount > 0);
--cast->refcount;
logprint(INFO, "xdpw: screencast instance %p has %d references", cast, cast->refcount);
if (cast->refcount < 1) {
cast->quit = true;
}
}
sd_bus_slot_unref(sess->slot);
wl_list_remove(&sess->link);
free(sess->session_handle);
free(sess);
} }

View file

@ -1,5 +1,15 @@
#include "pipewire_screencast.h" #include "pipewire_screencast.h"
#include <pipewire/pipewire.h>
#include <spa/utils/result.h>
#include <spa/param/props.h>
#include <spa/param/format-utils.h>
#include <spa/param/video/format-utils.h>
#include "wlr_screencast.h"
#include "xdpw.h"
#include "logger.h"
static void writeFrameData(void *pwFramePointer, void *wlrFramePointer, static void writeFrameData(void *pwFramePointer, void *wlrFramePointer,
uint32_t height, uint32_t stride, bool inverted) { uint32_t height, uint32_t stride, bool inverted) {
if (!inverted) { if (!inverted) {
@ -17,8 +27,7 @@ static void writeFrameData(void *pwFramePointer, void *wlrFramePointer,
} }
static void pwr_on_event(void *data, uint64_t expirations) { static void pwr_on_event(void *data, uint64_t expirations) {
struct xdpw_state *state = data; struct xdpw_screencast_instance *cast = data;
struct screencast_context *ctx = &state->screencast;
struct pw_buffer *pw_buf; struct pw_buffer *pw_buf;
struct spa_buffer *spa_buf; struct spa_buffer *spa_buf;
struct spa_meta_header *h; struct spa_meta_header *h;
@ -27,7 +36,7 @@ static void pwr_on_event(void *data, uint64_t expirations) {
logprint(TRACE, "********************"); logprint(TRACE, "********************");
logprint(TRACE, "pipewire: event fired"); logprint(TRACE, "pipewire: event fired");
if ((pw_buf = pw_stream_dequeue_buffer(ctx->stream)) == NULL) { if ((pw_buf = pw_stream_dequeue_buffer(cast->stream)) == NULL) {
logprint(WARN, "pipewire: out of buffers"); logprint(WARN, "pipewire: out of buffers");
return; return;
} }
@ -41,60 +50,58 @@ static void pwr_on_event(void *data, uint64_t expirations) {
if ((h = spa_buffer_find_meta_data(spa_buf, SPA_META_Header, sizeof(*h)))) { if ((h = spa_buffer_find_meta_data(spa_buf, SPA_META_Header, sizeof(*h)))) {
h->pts = -1; h->pts = -1;
h->flags = 0; h->flags = 0;
h->seq = ctx->seq++; h->seq = cast->seq++;
h->dts_offset = 0; h->dts_offset = 0;
} }
d[0].type = SPA_DATA_MemPtr; d[0].type = SPA_DATA_MemPtr;
d[0].maxsize = ctx->simple_frame.size; d[0].maxsize = cast->simple_frame.size;
d[0].mapoffset = 0; d[0].mapoffset = 0;
d[0].chunk->size = ctx->simple_frame.size; d[0].chunk->size = cast->simple_frame.size;
d[0].chunk->stride = ctx->simple_frame.stride; d[0].chunk->stride = cast->simple_frame.stride;
d[0].chunk->offset = 0; d[0].chunk->offset = 0;
d[0].flags = 0; d[0].flags = 0;
d[0].fd = -1; d[0].fd = -1;
writeFrameData(d[0].data, ctx->simple_frame.data, ctx->simple_frame.height, writeFrameData(d[0].data, cast->simple_frame.data, cast->simple_frame.height,
ctx->simple_frame.stride, ctx->simple_frame.y_invert); cast->simple_frame.stride, cast->simple_frame.y_invert);
logprint(TRACE, "pipewire: pointer %p", d[0].data); logprint(TRACE, "pipewire: pointer %p", d[0].data);
logprint(TRACE, "pipewire: size %d", d[0].maxsize); logprint(TRACE, "pipewire: size %d", d[0].maxsize);
logprint(TRACE, "pipewire: stride %d", d[0].chunk->stride); logprint(TRACE, "pipewire: stride %d", d[0].chunk->stride);
logprint(TRACE, "pipewire: width %d", ctx->simple_frame.width); logprint(TRACE, "pipewire: width %d", cast->simple_frame.width);
logprint(TRACE, "pipewire: height %d", ctx->simple_frame.height); logprint(TRACE, "pipewire: height %d", cast->simple_frame.height);
logprint(TRACE, "pipewire: y_invert %d", ctx->simple_frame.y_invert); logprint(TRACE, "pipewire: y_invert %d", cast->simple_frame.y_invert);
logprint(TRACE, "********************"); logprint(TRACE, "********************");
pw_stream_queue_buffer(ctx->stream, pw_buf); pw_stream_queue_buffer(cast->stream, pw_buf);
wlr_frame_free(state); xdpw_wlr_frame_free(cast);
} }
static void pwr_handle_stream_state_changed(void *data, static void pwr_handle_stream_state_changed(void *data,
enum pw_stream_state old, enum pw_stream_state state, const char *error) { enum pw_stream_state old, enum pw_stream_state state, const char *error) {
struct xdpw_state *xdpw_state = data; struct xdpw_screencast_instance *cast = data;
struct screencast_context *ctx = &xdpw_state->screencast; cast->node_id = pw_stream_get_node_id(cast->stream);
ctx->node_id = pw_stream_get_node_id(ctx->stream);
logprint(INFO, "pipewire: stream state changed to \"%s\"", logprint(INFO, "pipewire: stream state changed to \"%s\"",
pw_stream_state_as_string(state)); pw_stream_state_as_string(state));
logprint(INFO, "pipewire: node id is %d", ctx->node_id); logprint(INFO, "pipewire: node id is %d", cast->node_id);
switch (state) { switch (state) {
case PW_STREAM_STATE_STREAMING: case PW_STREAM_STATE_STREAMING:
ctx->stream_state = true; cast->pwr_stream_state = true;
break; break;
default: default:
ctx->stream_state = false; cast->pwr_stream_state = false;
break; break;
} }
} }
static void pwr_handle_stream_param_changed(void *data, uint32_t id, static void pwr_handle_stream_param_changed(void *data, uint32_t id,
const struct spa_pod *param) { const struct spa_pod *param) {
struct xdpw_state *xdpw_state = data; struct xdpw_screencast_instance *cast = data;
struct screencast_context *ctx = &xdpw_state->screencast; struct pw_stream *stream = cast->stream;
struct pw_stream *stream = ctx->stream;
uint8_t params_buffer[1024]; uint8_t params_buffer[1024];
struct spa_pod_builder b = struct spa_pod_builder b =
SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer));
@ -104,14 +111,14 @@ static void pwr_handle_stream_param_changed(void *data, uint32_t id,
return; return;
} }
spa_format_video_raw_parse(param, &ctx->pwr_format); spa_format_video_raw_parse(param, &cast->pwr_format);
params[0] = spa_pod_builder_add_object(&b, params[0] = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(BUFFERS, 1, 32), SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(BUFFERS, 1, 32),
SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1),
SPA_PARAM_BUFFERS_size, SPA_POD_Int(ctx->simple_frame.size), SPA_PARAM_BUFFERS_size, SPA_POD_Int(cast->simple_frame.size),
SPA_PARAM_BUFFERS_stride, SPA_POD_Int(ctx->simple_frame.stride), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(cast->simple_frame.stride),
SPA_PARAM_BUFFERS_align, SPA_POD_Int(ALIGN)); SPA_PARAM_BUFFERS_align, SPA_POD_Int(ALIGN));
params[1] = spa_pod_builder_add_object(&b, params[1] = spa_pod_builder_add_object(&b,
@ -128,66 +135,89 @@ static const struct pw_stream_events pwr_stream_events = {
.param_changed = pwr_handle_stream_param_changed, .param_changed = pwr_handle_stream_param_changed,
}; };
void *pwr_start(struct xdpw_state *state) { void xdpw_pwr_stream_init(struct xdpw_screencast_instance *cast) {
struct screencast_context *ctx = &state->screencast; struct xdpw_screencast_context *ctx = cast->ctx;
struct xdpw_state *state = ctx->state;
pw_loop_enter(state->pw_loop); pw_loop_enter(state->pw_loop);
const struct spa_pod *params[1]; const struct spa_pod *params[1];
uint8_t buffer[1024]; uint8_t buffer[1024];
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
ctx->pwr_context = pw_context_new(state->pw_loop, NULL, 0); char name[] = "xdpw-stream-XXXXXX";
if (!ctx->pwr_context) { randname(name + strlen(name) - 6);
logprint(ERROR, "pipewire: failed to create context"); cast->stream = pw_stream_new(ctx->core, name,
abort();
}
ctx->core = pw_context_connect(ctx->pwr_context, NULL, 0);
if (!ctx->core) {
logprint(ERROR, "pipewire: couldn't connect to context");
abort();
}
ctx->stream = pw_stream_new(ctx->core, "xdg-desktop-portal-wlr",
pw_properties_new( pw_properties_new(
PW_KEY_MEDIA_CLASS, "Video/Source", PW_KEY_MEDIA_CLASS, "Video/Source",
NULL)); NULL));
if (!ctx->stream) { if (!cast->stream) {
logprint(ERROR, "pipewire: failed to create stream"); logprint(ERROR, "pipewire: failed to create stream");
abort(); abort();
} }
ctx->stream_state = false; cast->pwr_stream_state = false;
/* make an event to signal frame ready */ /* make an event to signal frame ready */
ctx->event = cast->event =
pw_loop_add_event(state->pw_loop, pwr_on_event, state); pw_loop_add_event(state->pw_loop, pwr_on_event, cast);
logprint(TRACE, "pipewire: registered event %p", cast->event);
params[0] = spa_pod_builder_add_object(&b, params[0] = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
SPA_FORMAT_VIDEO_format, SPA_POD_Id(pipewire_from_wl_shm(ctx)), SPA_FORMAT_VIDEO_format, SPA_POD_Id(xdpw_format_pw_from_wl_shm(cast)),
SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(
&SPA_RECTANGLE(ctx->simple_frame.width, ctx->simple_frame.height), &SPA_RECTANGLE(cast->simple_frame.width, cast->simple_frame.height),
&SPA_RECTANGLE(1, 1), &SPA_RECTANGLE(1, 1),
&SPA_RECTANGLE(4096, 4096)), &SPA_RECTANGLE(4096, 4096)),
// variable framerate // variable framerate
SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&SPA_FRACTION(0, 1)), SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&SPA_FRACTION(0, 1)),
SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_CHOICE_RANGE_Fraction( SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_CHOICE_RANGE_Fraction(
&SPA_FRACTION(ctx->framerate, 1), &SPA_FRACTION(cast->framerate, 1),
&SPA_FRACTION(1, 1), &SPA_FRACTION(1, 1),
&SPA_FRACTION(ctx->framerate, 1))); &SPA_FRACTION(cast->framerate, 1)));
pw_stream_add_listener (ctx->stream, &ctx->stream_listener, pw_stream_add_listener(cast->stream, &cast->stream_listener,
&pwr_stream_events, state); &pwr_stream_events, cast);
pw_stream_connect(ctx->stream, pw_stream_connect(cast->stream,
PW_DIRECTION_OUTPUT, PW_DIRECTION_OUTPUT,
PW_ID_ANY, PW_ID_ANY,
(PW_STREAM_FLAG_DRIVER | (PW_STREAM_FLAG_DRIVER |
PW_STREAM_FLAG_MAP_BUFFERS), PW_STREAM_FLAG_MAP_BUFFERS),
params, 1); params, 1);
return NULL; }
int xdpw_pwr_core_connect(struct xdpw_state *state) {
struct xdpw_screencast_context *ctx = &state->screencast;
logprint(TRACE, "pipewire: establishing connection to core");
if (!ctx->pwr_context) {
ctx->pwr_context = pw_context_new(state->pw_loop, NULL, 0);
if (!ctx->pwr_context) {
logprint(ERROR, "pipewire: failed to create context");
return -1;
}
}
if (!ctx->core) {
ctx->core = pw_context_connect(ctx->pwr_context, NULL, 0);
if (!ctx->core) {
logprint(ERROR, "pipewire: couldn't connect to context");
return -1;
}
}
return 0;
}
void xdpw_pwr_stream_destroy(struct xdpw_screencast_instance *cast) {
logprint(TRACE, "pipewire: destroying stream");
pw_stream_flush(cast->stream, false);
pw_stream_disconnect(cast->stream);
pw_stream_destroy(cast->stream);
cast->stream = NULL;
} }

View file

@ -1,61 +1,115 @@
#define _POSIX_C_SOURCE 200809L
#include "screencast.h" #include "screencast.h"
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <spa/utils/result.h>
#include "pipewire_screencast.h"
#include "wlr_screencast.h"
#include "xdpw.h"
#include "logger.h"
static const char object_path[] = "/org/freedesktop/portal/desktop"; static const char object_path[] = "/org/freedesktop/portal/desktop";
static const char interface_name[] = "org.freedesktop.impl.portal.ScreenCast"; static const char interface_name[] = "org.freedesktop.impl.portal.ScreenCast";
int setup_outputs(struct xdpw_state *state) { void xdpw_screencast_instance_init(struct xdpw_screencast_context *ctx,
struct xdpw_screencast_instance *cast, struct xdpw_wlr_output *out, bool with_cursor) {
cast->ctx = ctx;
cast->target_output = out;
cast->framerate = out->framerate;
cast->with_cursor = with_cursor;
cast->refcount = 1;
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",
wl_list_length(&ctx->screencast_instances));
}
struct screencast_context *ctx = &state->screencast; void xdpw_screencast_instance_destroy(struct xdpw_screencast_instance *cast) {
assert(cast->refcount == 0);
logprint(TRACE, "xdpw: destroying cast instance");
wl_list_remove(&cast->link);
xdpw_pwr_stream_destroy(cast);
free(cast);
}
struct wayland_output *output, *tmp_o; int setup_outputs(struct xdpw_screencast_context *ctx, struct xdpw_session *sess, bool with_cursor) {
struct xdpw_wlr_output *output, *tmp_o;
wl_list_for_each_reverse_safe(output, tmp_o, &ctx->output_list, link) { wl_list_for_each_reverse_safe(output, tmp_o, &ctx->output_list, link) {
logprint(INFO, "wlroots: capturable output: %s model: %s: id: %i name: %s", logprint(INFO, "wlroots: capturable output: %s model: %s: id: %i name: %s",
output->make, output->model, output->id, output->name); output->make, output->model, output->id, output->name);
} }
struct wayland_output *out; struct xdpw_wlr_output *out;
if (ctx->output_name) { if (ctx->output_name) {
out = wlr_output_find_by_name(&ctx->output_list, ctx->output_name); out = xdpw_wlr_output_find_by_name(&ctx->output_list, ctx->output_name);
if (!out) { if (!out) {
logprint(ERROR, "wlroots: no such output"); logprint(ERROR, "wlroots: no such output");
abort(); abort();
} }
} else { } else {
out = wlr_output_first(&ctx->output_list); out = xdpw_wlr_output_first(&ctx->output_list);
if (!out) { if (!out) {
logprint(ERROR, "wlroots: no output found"); logprint(ERROR, "wlroots: no output found");
abort(); abort();
} }
} }
ctx->target_output = out; struct xdpw_screencast_instance *cast, *tmp_c;
ctx->framerate = out->framerate; wl_list_for_each_reverse_safe(cast, tmp_c, &ctx->screencast_instances, link) {
ctx->with_cursor = true; logprint(INFO, "xdpw: existing screencast instance: %d %s cursor",
cast->target_output->id,
cast->with_cursor ? "with" : "without");
logprint(INFO, "wlroots: output: %s", ctx->target_output->name); if (cast->target_output->id == out->id && cast->with_cursor == with_cursor) {
logprint(INFO, "wlroots: wl_display fd: %d", wl_display_get_fd(state->wl_display)); sess->screencast_instance = cast;
++cast->refcount;
logprint(INFO, "xdpw: screencast instance %p has %d references", cast, cast->refcount);
}
}
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);
}
logprint(INFO, "wlroots: output: %s",
sess->screencast_instance->target_output->name);
return 0; return 0;
} }
void *start_screencast(void *data) { static int start_screencast(void *data) {
struct xdpw_state *state = data; struct xdpw_screencast_instance *cast = data;
xdpw_wlr_register_cb(cast);
wlr_register_cb(state);
// process at least one frame so that we know // process at least one frame so that we know
// some of the metadata required for the pipewire // some of the metadata required for the pipewire
// remote state connected event // remote state connected event
wl_display_dispatch(state->wl_display); wl_display_dispatch(cast->ctx->state->wl_display);
wl_display_roundtrip(state->wl_display); wl_display_roundtrip(cast->ctx->state->wl_display);
pwr_start(state); xdpw_pwr_stream_init(cast);
return NULL; cast->initialized = true;
return 0;
} }
static int method_screencast_create_session(sd_bus_message *msg, void *data, static int method_screencast_create_session(sd_bus_message *msg, void *data,
sd_bus_error *ret_error) { sd_bus_error *ret_error) {
struct xdpw_state *state = data;
int ret = 0; int ret = 0;
logprint(INFO, "dbus: create session method invoked"); logprint(INFO, "dbus: create session method invoked");
@ -106,16 +160,14 @@ static int method_screencast_create_session(sd_bus_message *msg, void *data,
return ret; return ret;
} }
// TODO: cleanup this
struct xdpw_request *req = struct xdpw_request *req =
request_create(sd_bus_message_get_bus(msg), request_handle); xdpw_request_create(sd_bus_message_get_bus(msg), request_handle);
if (req == NULL) { if (req == NULL) {
return -ENOMEM; return -ENOMEM;
} }
// TODO: cleanup this
struct xdpw_session *sess = struct xdpw_session *sess =
session_create(sd_bus_message_get_bus(msg), session_handle); xdpw_session_create(state, sd_bus_message_get_bus(msg), strdup(session_handle));
if (sess == NULL) { if (sess == NULL) {
return -ENOMEM; return -ENOMEM;
} }
@ -138,16 +190,19 @@ static int method_screencast_create_session(sd_bus_message *msg, void *data,
return 0; return 0;
} }
static int method_screencast_select_sources(sd_bus_message *msg, void *data, static int method_screencast_select_sources(sd_bus_message *msg, void *data,
sd_bus_error *ret_error) { sd_bus_error *ret_error) {
struct xdpw_state *state = data; struct xdpw_state *state = data;
struct xdpw_screencast_context *ctx = &state->screencast;
int ret = 0; int ret = 0;
struct xdpw_session *sess, *tmp_s;
sd_bus_message *reply = NULL;
logprint(INFO, "dbus: select sources method invoked"); logprint(INFO, "dbus: select sources method invoked");
setup_outputs(state); // default to embedded cursor mode if not specified
bool cursor_embedded = true;
char *request_handle, *session_handle, *app_id; char *request_handle, *session_handle, *app_id;
ret = sd_bus_message_read(msg, "oos", &request_handle, &session_handle, &app_id); ret = sd_bus_message_read(msg, "oos", &request_handle, &session_handle, &app_id);
@ -178,7 +233,22 @@ static int method_screencast_select_sources(sd_bus_message *msg, void *data,
} else if (strcmp(key, "types") == 0) { } else if (strcmp(key, "types") == 0) {
uint32_t mask; uint32_t mask;
sd_bus_message_read(msg, "v", "u", &mask); sd_bus_message_read(msg, "v", "u", &mask);
if (mask & (1<<WINDOW)) {
logprint(INFO, "dbus: non-monitor cast requested, not replying");
return -1;
}
logprint(INFO, "dbus: option types:%x", mask); 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 & (1<<HIDDEN)) {
cursor_embedded = false;
}
if (cursor_mode & (1<<METADATA)) {
logprint(ERROR, "dbus: unsupported cursor mode requested, cancelling");
goto error;
}
logprint(INFO, "dbus: option cursor_mode:%x", cursor_mode);
} else { } else {
logprint(WARN, "dbus: unknown option %s", key); logprint(WARN, "dbus: unknown option %s", key);
sd_bus_message_skip(msg, "v"); sd_bus_message_skip(msg, "v");
@ -197,8 +267,17 @@ static int method_screencast_select_sources(sd_bus_message *msg, void *data,
return ret; return ret;
} }
ret = -1;
wl_list_for_each_reverse_safe(sess, tmp_s, &state->xdpw_sessions, link) {
if (strcmp(sess->session_handle, session_handle) == 0) {
logprint(TRACE, "dbus: select sources: found matching session %s", sess->session_handle);
ret = setup_outputs(ctx, sess, cursor_embedded);
}
}
if (ret < 0) {
return ret;
}
sd_bus_message *reply = NULL;
ret = sd_bus_message_new_method_return(msg, &reply); ret = sd_bus_message_new_method_return(msg, &reply);
if (ret < 0) { if (ret < 0) {
return ret; return ret;
@ -211,22 +290,41 @@ static int method_screencast_select_sources(sd_bus_message *msg, void *data,
if (ret < 0) { if (ret < 0) {
return ret; return ret;
} }
sd_bus_message_unref(reply); sd_bus_message_unref(reply);
return 0; 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(TRACE, "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, static int method_screencast_start(sd_bus_message *msg, void *data,
sd_bus_error *ret_error) { sd_bus_error *ret_error) {
struct xdpw_state *state = data; struct xdpw_state *state = data;
struct screencast_context *ctx = &state->screencast;
int ret = 0; int ret = 0;
logprint(INFO, "dbus: start method invoked"); logprint(INFO, "dbus: start method invoked");
start_screencast(state);
char *request_handle, *session_handle, *app_id, *parent_window; char *request_handle, *session_handle, *app_id, *parent_window;
ret = sd_bus_message_read(msg, "ooss", &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) { if (ret < 0) {
@ -249,10 +347,8 @@ static int method_screencast_start(sd_bus_message *msg, void *data,
if (innerRet < 0) { if (innerRet < 0) {
return innerRet; return innerRet;
} }
logprint(WARN, "dbus: unknown option: %s", key); logprint(WARN, "dbus: unknown option: %s", key);
sd_bus_message_skip(msg, "v"); sd_bus_message_skip(msg, "v");
innerRet = sd_bus_message_exit_container(msg); innerRet = sd_bus_message_exit_container(msg);
if (innerRet < 0) { if (innerRet < 0) {
return innerRet; return innerRet;
@ -266,24 +362,40 @@ static int method_screencast_start(sd_bus_message *msg, void *data,
return ret; return ret;
} }
struct xdpw_screencast_instance *cast = NULL;
struct xdpw_session *sess, *tmp_s;
wl_list_for_each_reverse_safe(sess, tmp_s, &state->xdpw_sessions, link) {
if (strcmp(sess->session_handle, session_handle) == 0) {
logprint(TRACE, "dbus: start: found matching session %s", sess->session_handle);
cast = sess->screencast_instance;
}
}
if (!cast) {
return -1;
}
if (!cast->initialized) {
start_screencast(cast);
}
while (cast->node_id == 0) {
int ret = pw_loop_iterate(state->pw_loop, 0);
if (ret != 0) {
logprint(ERROR, "pipewire_loop_iterate failed: %s", spa_strerror(ret));
}
}
sd_bus_message *reply = NULL; sd_bus_message *reply = NULL;
ret = sd_bus_message_new_method_return(msg, &reply); ret = sd_bus_message_new_method_return(msg, &reply);
if (ret < 0) { if (ret < 0) {
return ret; return ret;
} }
while (ctx->node_id == 0) {
int ret = pw_loop_iterate(state->pw_loop, 0);
if (ret < 0) {
logprint(ERROR, "pipewire_loop_iterate failed: %s", spa_strerror(ret));
}
}
ret = sd_bus_message_append(reply, "ua{sv}", PORTAL_RESPONSE_SUCCESS, 1, ret = sd_bus_message_append(reply, "ua{sv}", PORTAL_RESPONSE_SUCCESS, 1,
"streams", "a(ua{sv})", 1, "streams", "a(ua{sv})", 1,
ctx->node_id, 2, cast->node_id, 2,
"position", "(ii)", 0, 0, "position", "(ii)", 0, 0,
"size", "(ii)", ctx->simple_frame.width, ctx->simple_frame.height); "size", "(ii)", cast->simple_frame.width, cast->simple_frame.height);
if (ret < 0) { if (ret < 0) {
return ret; return ret;
@ -306,20 +418,33 @@ static const sd_bus_vtable screencast_vtable[] = {
method_screencast_select_sources, SD_BUS_VTABLE_UNPRIVILEGED), method_screencast_select_sources, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("Start", "oossa{sv}", "ua{sv}", SD_BUS_METHOD("Start", "oossa{sv}", "ua{sv}",
method_screencast_start, SD_BUS_VTABLE_UNPRIVILEGED), method_screencast_start, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_PROPERTY("AvailableSourceTypes", "u", NULL,
offsetof(struct xdpw_state, screencast_source_types),
SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("AvailableCursorModes", "u", NULL,
offsetof(struct xdpw_state, screencast_cursor_modes),
SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("version", "u", NULL,
offsetof(struct xdpw_state, screencast_version),
SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_VTABLE_END SD_BUS_VTABLE_END
}; };
int init_screencast(struct xdpw_state *state, const char *output_name, const char *forced_pixelformat) { int xdpw_screencast_init(struct xdpw_state *state, const char *output_name, const char *forced_pixelformat) {
// TODO: cleanup
sd_bus_slot *slot = NULL; sd_bus_slot *slot = NULL;
state->screencast = (struct screencast_context) { 0 }; state->screencast = (struct xdpw_screencast_context) { 0 };
state->screencast.state = state;
state->screencast.forced_pixelformat = forced_pixelformat; state->screencast.forced_pixelformat = forced_pixelformat;
state->screencast.output_name = output_name; state->screencast.output_name = output_name;
state->screencast.simple_frame = (struct simple_frame) { 0 };
state->screencast.simple_frame.damage = &(struct damage) { 0 };
int err = wlr_screencopy_init(state); int err;
err = xdpw_pwr_core_connect(state);
if (err) {
goto end;
}
err = xdpw_wlr_screencopy_init(state);
if (err) { if (err) {
goto end; goto end;
} }
@ -328,6 +453,6 @@ int init_screencast(struct xdpw_state *state, const char *output_name, const cha
screencast_vtable, state); screencast_vtable, state);
end: end:
wlr_screencopy_uninit(&state->screencast); xdpw_wlr_screencopy_finish(&state->screencast);
return err; return err;
} }

View file

@ -1,18 +1,30 @@
#include "screencast_common.h" #include "screencast_common.h"
#include <assert.h>
enum spa_video_format pipewire_from_wl_shm(void *data) { void randname(char *buf) {
struct screencast_context *ctx = data; struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
long r = ts.tv_nsec;
for (int i = 0; i < 6; ++i) {
assert(buf[i] == 'X');
buf[i] = 'A'+(r&15)+(r&16)*2;
r >>= 5;
}
}
if (ctx->forced_pixelformat) { enum spa_video_format xdpw_format_pw_from_wl_shm(void *data) {
if (strcmp(ctx->forced_pixelformat, "BGRx") == 0) { struct xdpw_screencast_instance *cast = data;
if (cast->ctx->forced_pixelformat) {
if (strcmp(cast->ctx->forced_pixelformat, "BGRx") == 0) {
return SPA_VIDEO_FORMAT_BGRx; return SPA_VIDEO_FORMAT_BGRx;
} }
if (strcmp(ctx->forced_pixelformat, "RGBx") == 0) { if (strcmp(cast->ctx->forced_pixelformat, "RGBx") == 0) {
return SPA_VIDEO_FORMAT_RGBx; return SPA_VIDEO_FORMAT_RGBx;
} }
} }
switch (ctx->simple_frame.format) { switch (cast->simple_frame.format) {
case WL_SHM_FORMAT_ARGB8888: case WL_SHM_FORMAT_ARGB8888:
return SPA_VIDEO_FORMAT_BGRA; return SPA_VIDEO_FORMAT_BGRA;
case WL_SHM_FORMAT_XRGB8888: case WL_SHM_FORMAT_XRGB8888:

View file

@ -1,31 +1,74 @@
#define _POSIX_C_SOURCE 200809L #define _POSIX_C_SOURCE 200809L
#include "wlr_screencast.h" #include "wlr_screencast.h"
#include "xdpw.h"
void wlr_frame_free(struct xdpw_state *state) { #include "wlr-screencopy-unstable-v1-client-protocol.h"
zwlr_screencopy_frame_v1_destroy(state->screencast.wlr_frame); #include "xdg-output-unstable-v1-client-protocol.h"
munmap(state->screencast.simple_frame.data, state->screencast.simple_frame.size); #include <fcntl.h>
wl_buffer_destroy(state->screencast.simple_frame.buffer); #include <limits.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.h"
#include "pipewire_screencast.h"
#include "xdpw.h"
#include "logger.h"
void xdpw_wlr_frame_free(struct xdpw_screencast_instance *cast) {
zwlr_screencopy_frame_v1_destroy(cast->wlr_frame);
cast->wlr_frame = NULL;
munmap(cast->simple_frame.data, cast->simple_frame.size);
cast->simple_frame.data = NULL;
// TODO: reuse this buffer unless we quit or error out
wl_buffer_destroy(cast->simple_frame.buffer);
cast->simple_frame.buffer = NULL;
logprint(TRACE, "wlroots: frame destroyed"); logprint(TRACE, "wlroots: frame destroyed");
if (!state->screencast.quit && !state->screencast.err) { if (cast->quit || cast->err) {
wlr_register_cb(state); xdpw_screencast_instance_destroy(cast);
return ;
} }
xdpw_wlr_register_cb(cast);
} }
static struct wl_buffer *create_shm_buffer(struct screencast_context *ctx, static int anonymous_shm_open(void) {
char name[] = "/xdpw-shm-XXXXXX";
int retries = 100;
do {
randname(name + strlen(name) - 6);
--retries;
// shm_open guarantees that O_CLOEXEC is set
int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
if (fd >= 0) {
shm_unlink(name);
return fd;
}
} while (retries > 0 && errno == EEXIST);
return -1;
}
static struct wl_buffer *create_shm_buffer(struct xdpw_screencast_instance *cast,
enum wl_shm_format fmt, int width, int height, int stride, enum wl_shm_format fmt, int width, int height, int stride,
void **data_out) { void **data_out) {
struct xdpw_screencast_context *ctx = cast->ctx;
int size = stride * height; int size = stride * height;
const char shm_name[] = "/wlroots-screencopy"; int fd = anonymous_shm_open();
int fd = shm_open(shm_name, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
if (fd < 0) { if (fd < 0) {
logprint(ERROR, "wlroots: shm_open failed"); logprint(ERROR, "wlroots: shm_open failed");
return NULL; return NULL;
} }
shm_unlink(shm_name);
int ret; int ret;
while ((ret = ftruncate(fd, size)) == EINTR); while ((ret = ftruncate(fd, size)) == EINTR);
@ -55,77 +98,72 @@ static struct wl_buffer *create_shm_buffer(struct screencast_context *ctx,
static void wlr_frame_buffer(void *data, struct zwlr_screencopy_frame_v1 *frame, 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) { uint32_t format, uint32_t width, uint32_t height, uint32_t stride) {
struct xdpw_state *state = data; struct xdpw_screencast_instance *cast = data;
struct screencast_context *ctx = &state->screencast;
logprint(TRACE, "wlroots: buffer event handler"); logprint(TRACE, "wlroots: buffer event handler");
ctx->wlr_frame = frame; cast->wlr_frame = frame;
ctx->simple_frame.width = width; cast->simple_frame.width = width;
ctx->simple_frame.height = height; cast->simple_frame.height = height;
ctx->simple_frame.stride = stride; cast->simple_frame.stride = stride;
ctx->simple_frame.size = stride * height; cast->simple_frame.size = stride * height;
ctx->simple_frame.format = format; cast->simple_frame.format = format;
ctx->simple_frame.buffer = create_shm_buffer(ctx, format, width, height, cast->simple_frame.buffer = create_shm_buffer(cast, format, width, height,
stride, &ctx->simple_frame.data); stride, &cast->simple_frame.data);
if (ctx->simple_frame.buffer == NULL) { if (cast->simple_frame.buffer == NULL) {
logprint(ERROR, "wlroots: failed to create buffer"); logprint(ERROR, "wlroots: failed to create buffer");
abort(); abort();
} }
zwlr_screencopy_frame_v1_copy_with_damage(frame, ctx->simple_frame.buffer); zwlr_screencopy_frame_v1_copy_with_damage(frame, cast->simple_frame.buffer);
logprint(TRACE, "wlroots: frame copied"); logprint(TRACE, "wlroots: frame copied");
} }
static void wlr_frame_flags(void *data, struct zwlr_screencopy_frame_v1 *frame, static void wlr_frame_flags(void *data, struct zwlr_screencopy_frame_v1 *frame,
uint32_t flags) { uint32_t flags) {
struct xdpw_state *state = data; struct xdpw_screencast_instance *cast = data;
struct screencast_context *ctx = &state->screencast;
logprint(TRACE, "wlroots: flags event handler"); logprint(TRACE, "wlroots: flags event handler");
ctx->simple_frame.y_invert = flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT; cast->simple_frame.y_invert = flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT;
} }
static void wlr_frame_ready(void *data, struct zwlr_screencopy_frame_v1 *frame, 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) { uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) {
struct xdpw_state *state = data; struct xdpw_screencast_instance *cast = data;
struct screencast_context *ctx = &state->screencast;
logprint(TRACE, "wlroots: ready event handler"); logprint(TRACE, "wlroots: ready event handler");
ctx->simple_frame.tv_sec = ((((uint64_t)tv_sec_hi) << 32) | tv_sec_lo); cast->simple_frame.tv_sec = ((((uint64_t)tv_sec_hi) << 32) | tv_sec_lo);
ctx->simple_frame.tv_nsec = tv_nsec; cast->simple_frame.tv_nsec = tv_nsec;
if (!ctx->quit && !ctx->err && ctx->stream_state) { if (!cast->quit && !cast->err && cast->pwr_stream_state) {
pw_loop_signal_event(state->pw_loop, ctx->event); pw_loop_signal_event(cast->ctx->state->pw_loop, cast->event);
return ; return ;
} }
wlr_frame_free(state); xdpw_wlr_frame_free(cast);
} }
static void wlr_frame_failed(void *data, static void wlr_frame_failed(void *data,
struct zwlr_screencopy_frame_v1 *frame) { struct zwlr_screencopy_frame_v1 *frame) {
struct xdpw_state *state = data; struct xdpw_screencast_instance *cast = data;
struct screencast_context *ctx = &state->screencast;
logprint(TRACE, "wlroots: failed event handler"); logprint(TRACE, "wlroots: failed event handler");
ctx->err = true; cast->err = true;
wlr_frame_free(state); xdpw_wlr_frame_free(cast);
} }
static void wlr_frame_damage(void *data, struct zwlr_screencopy_frame_v1 *frame, 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) { uint32_t x, uint32_t y, uint32_t width, uint32_t height) {
struct xdpw_state *state = data; struct xdpw_screencast_instance *cast = data;
struct screencast_context *ctx = &state->screencast;
logprint(TRACE, "wlroots: damage event handler"); logprint(TRACE, "wlroots: damage event handler");
ctx->simple_frame.damage->x = x; cast->simple_frame.damage.x = x;
ctx->simple_frame.damage->y = y; cast->simple_frame.damage.y = y;
ctx->simple_frame.damage->width = width; cast->simple_frame.damage.width = width;
ctx->simple_frame.damage->height = height; cast->simple_frame.damage.height = height;
} }
static const struct zwlr_screencopy_frame_v1_listener wlr_frame_listener = { static const struct zwlr_screencopy_frame_v1_listener wlr_frame_listener = {
@ -136,21 +174,20 @@ static const struct zwlr_screencopy_frame_v1_listener wlr_frame_listener = {
.damage = wlr_frame_damage, .damage = wlr_frame_damage,
}; };
void wlr_register_cb(struct xdpw_state *state) { void xdpw_wlr_register_cb(struct xdpw_screencast_instance *cast) {
struct screencast_context *ctx = &state->screencast;
ctx->frame_callback = zwlr_screencopy_manager_v1_capture_output( cast->frame_callback = zwlr_screencopy_manager_v1_capture_output(
ctx->screencopy_manager, ctx->with_cursor, ctx->target_output->output); cast->ctx->screencopy_manager, cast->with_cursor, cast->target_output->output);
zwlr_screencopy_frame_v1_add_listener(ctx->frame_callback, zwlr_screencopy_frame_v1_add_listener(cast->frame_callback,
&wlr_frame_listener, state); &wlr_frame_listener, cast);
logprint(TRACE, "wlroots: callbacks registered"); logprint(TRACE, "wlroots: callbacks registered");
} }
static void wlr_output_handle_geometry(void *data, struct wl_output *wl_output, 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 x, int32_t y, int32_t phys_width, int32_t phys_height,
int32_t subpixel, const char *make, const char *model, int32_t transform) { int32_t subpixel, const char *make, const char *model, int32_t transform) {
struct wayland_output *output = data; struct xdpw_wlr_output *output = data;
output->make = strdup(make); output->make = strdup(make);
output->model = strdup(model); output->model = strdup(model);
} }
@ -158,7 +195,7 @@ static void wlr_output_handle_geometry(void *data, struct wl_output *wl_output,
static void wlr_output_handle_mode(void *data, struct wl_output *wl_output, 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) { uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
if (flags & WL_OUTPUT_MODE_CURRENT) { if (flags & WL_OUTPUT_MODE_CURRENT) {
struct wayland_output *output = data; struct xdpw_wlr_output *output = data;
output->framerate = (float)refresh/1000; output->framerate = (float)refresh/1000;
} }
} }
@ -181,7 +218,7 @@ static const struct wl_output_listener wlr_output_listener = {
static void wlr_xdg_output_name(void* data, struct zxdg_output_v1* xdg_output, static void wlr_xdg_output_name(void* data, struct zxdg_output_v1* xdg_output,
const char* name) { const char* name) {
struct wayland_output *output = data; struct xdpw_wlr_output *output = data;
output->name = strdup(name); output->name = strdup(name);
}; };
@ -198,34 +235,34 @@ static const struct zxdg_output_v1_listener wlr_xdg_output_listener = {
.name = wlr_xdg_output_name, .name = wlr_xdg_output_name,
}; };
void wlr_add_xdg_output_listener(struct wayland_output *output, static void wlr_add_xdg_output_listener(struct xdpw_wlr_output *output,
struct zxdg_output_v1* xdg_output) { struct zxdg_output_v1* xdg_output) {
output->xdg_output = xdg_output; output->xdg_output = xdg_output;
zxdg_output_v1_add_listener(output->xdg_output, &wlr_xdg_output_listener, zxdg_output_v1_add_listener(output->xdg_output, &wlr_xdg_output_listener,
output); output);
} }
static void wlr_init_xdg_outputs(struct screencast_context *ctx) { static void wlr_init_xdg_outputs(struct xdpw_screencast_context *ctx) {
struct wayland_output *output, *tmp; struct xdpw_wlr_output *output, *tmp;
wl_list_for_each_safe(output, tmp, &ctx->output_list, link) { wl_list_for_each_safe(output, tmp, &ctx->output_list, link) {
struct zxdg_output_v1 *xdg_output = struct zxdg_output_v1 *xdg_output =
zxdg_output_manager_v1_get_xdg_output( ctx->xdg_output_manager, zxdg_output_manager_v1_get_xdg_output(ctx->xdg_output_manager,
output->output); output->output);
wlr_add_xdg_output_listener(output, xdg_output); wlr_add_xdg_output_listener(output, xdg_output);
} }
} }
struct wayland_output *wlr_output_first(struct wl_list *output_list) { struct xdpw_wlr_output *xdpw_wlr_output_first(struct wl_list *output_list) {
struct wayland_output *output, *tmp; struct xdpw_wlr_output *output, *tmp;
wl_list_for_each_safe(output, tmp, output_list, link) { wl_list_for_each_safe(output, tmp, output_list, link) {
return output; return output;
} }
return NULL; return NULL;
} }
struct wayland_output *wlr_output_find_by_name(struct wl_list *output_list, struct xdpw_wlr_output *xdpw_wlr_output_find_by_name(struct wl_list *output_list,
const char* name) { const char* name) {
struct wayland_output *output, *tmp; struct xdpw_wlr_output *output, *tmp;
wl_list_for_each_safe(output, tmp, output_list, link) { wl_list_for_each_safe(output, tmp, output_list, link) {
if (strcmp(output->name, name) == 0) { if (strcmp(output->name, name) == 0) {
return output; return output;
@ -234,9 +271,9 @@ struct wayland_output *wlr_output_find_by_name(struct wl_list *output_list,
return NULL; return NULL;
} }
struct wayland_output *wlr_output_find(struct screencast_context *ctx, struct xdpw_wlr_output *xdpw_wlr_output_find(struct xdpw_screencast_context *ctx,
struct wl_output *out, uint32_t id) { struct wl_output *out, uint32_t id) {
struct wayland_output *output, *tmp; struct xdpw_wlr_output *output, *tmp;
wl_list_for_each_safe(output, tmp, &ctx->output_list, link) { wl_list_for_each_safe(output, tmp, &ctx->output_list, link) {
if ((output->output == out) || (output->id == id)) { if ((output->output == out) || (output->id == id)) {
return output; return output;
@ -245,16 +282,16 @@ struct wayland_output *wlr_output_find(struct screencast_context *ctx,
return NULL; return NULL;
} }
static void wlr_remove_output(struct wayland_output *out) { static void wlr_remove_output(struct xdpw_wlr_output *out) {
wl_list_remove(&out->link); wl_list_remove(&out->link);
} }
static void wlr_registry_handle_add(void *data, struct wl_registry *reg, static void wlr_registry_handle_add(void *data, struct wl_registry *reg,
uint32_t id, const char *interface, uint32_t ver) { uint32_t id, const char *interface, uint32_t ver) {
struct screencast_context *ctx = data; struct xdpw_screencast_context *ctx = data;
if (!strcmp(interface, wl_output_interface.name)) { if (!strcmp(interface, wl_output_interface.name)) {
struct wayland_output *output = malloc(sizeof(*output)); struct xdpw_wlr_output *output = malloc(sizeof(*output));
output->id = id; output->id = id;
output->output = wl_registry_bind(reg, id, &wl_output_interface, 1); output->output = wl_registry_bind(reg, id, &wl_output_interface, 1);
@ -281,7 +318,7 @@ static void wlr_registry_handle_add(void *data, struct wl_registry *reg,
static void wlr_registry_handle_remove(void *data, struct wl_registry *reg, static void wlr_registry_handle_remove(void *data, struct wl_registry *reg,
uint32_t id) { uint32_t id) {
wlr_remove_output( wlr_remove_output(
wlr_output_find((struct screencast_context *)data, NULL, id)); xdpw_wlr_output_find((struct xdpw_screencast_context *)data, NULL, id));
} }
static const struct wl_registry_listener wlr_registry_listener = { static const struct wl_registry_listener wlr_registry_listener = {
@ -289,12 +326,15 @@ static const struct wl_registry_listener wlr_registry_listener = {
.global_remove = wlr_registry_handle_remove, .global_remove = wlr_registry_handle_remove,
}; };
int wlr_screencopy_init(struct xdpw_state *state) { int xdpw_wlr_screencopy_init(struct xdpw_state *state) {
struct screencast_context *ctx = &state->screencast; struct xdpw_screencast_context *ctx = &state->screencast;
// retrieve list of outputs // initialize a list of outputs
wl_list_init(&ctx->output_list); wl_list_init(&ctx->output_list);
// initialize a list of active screencast instances
wl_list_init(&ctx->screencast_instances);
// retrieve registry // retrieve registry
ctx->registry = wl_display_get_registry(state->wl_display); ctx->registry = wl_display_get_registry(state->wl_display);
wl_registry_add_listener(ctx->registry, &wlr_registry_listener, ctx); wl_registry_add_listener(ctx->registry, &wlr_registry_listener, ctx);
@ -327,15 +367,29 @@ int wlr_screencopy_init(struct xdpw_state *state) {
return 0; return 0;
} }
void wlr_screencopy_uninit(struct screencast_context *ctx) { void xdpw_wlr_screencopy_finish(struct xdpw_screencast_context *ctx) {
struct wayland_output *output, *tmp_o; struct xdpw_wlr_output *output, *tmp_o;
wl_list_for_each_safe(output, tmp_o, &ctx->output_list, link) { wl_list_for_each_safe(output, tmp_o, &ctx->output_list, link) {
wl_list_remove(&output->link); wl_list_remove(&output->link);
zxdg_output_v1_destroy(output->xdg_output); zxdg_output_v1_destroy(output->xdg_output);
wl_output_destroy(output->output); wl_output_destroy(output->output);
} }
struct xdpw_screencast_instance *cast, *tmp_c;
wl_list_for_each_safe(cast, tmp_c, &ctx->screencast_instances, link) {
cast->quit = true;
}
if (ctx->screencopy_manager) { if (ctx->screencopy_manager) {
zwlr_screencopy_manager_v1_destroy(ctx->screencopy_manager); zwlr_screencopy_manager_v1_destroy(ctx->screencopy_manager);
} }
if (ctx->shm) {
wl_shm_destroy(ctx->shm);
}
if (ctx->xdg_output_manager) {
zxdg_output_manager_v1_destroy(ctx->xdg_output_manager);
}
if (ctx->registry) {
wl_registry_destroy(ctx->registry);
}
} }

View file

@ -49,7 +49,7 @@ static int method_screenshot(sd_bus_message *msg, void *data,
// TODO: cleanup this // TODO: cleanup this
struct xdpw_request *req = struct xdpw_request *req =
request_create(sd_bus_message_get_bus(msg), handle); xdpw_request_create(sd_bus_message_get_bus(msg), handle);
if (req == NULL) { if (req == NULL) {
return -ENOMEM; return -ENOMEM;
} }
@ -90,7 +90,7 @@ static const sd_bus_vtable screenshot_vtable[] = {
SD_BUS_VTABLE_END SD_BUS_VTABLE_END
}; };
int init_screenshot(struct xdpw_state *state) { int xdpw_screenshot_init(struct xdpw_state *state) {
// TODO: cleanup // TODO: cleanup
sd_bus_slot *slot = NULL; sd_bus_slot *slot = NULL;
return sd_bus_add_object_vtable(state->bus, &slot, object_path, interface_name, return sd_bus_add_object_vtable(state->bus, &slot, object_path, interface_name,