Desktop portal capture: support rotated monitor capture on sway

This commit is contained in:
dec05eba
2025-07-29 23:30:00 +02:00
parent b7c8334679
commit a4b1ff28d5
4 changed files with 142 additions and 63 deletions

View File

@@ -30,27 +30,21 @@ typedef struct {
gsr_pipewire_video pipewire;
vec2i capture_size;
gsr_pipewire_video_dmabuf_data dmabuf_data[GSR_PIPEWIRE_VIDEO_DMABUF_MAX_PLANES];
int num_dmabuf_data;
gsr_pipewire_video_region region;
gsr_pipewire_video_region cursor_region;
uint32_t pipewire_fourcc;
uint64_t pipewire_modifiers;
bool using_external_image;
gsr_map_texture_output pipewire_data;
bool should_stop;
bool stop_is_error;
} gsr_capture_portal;
static void gsr_capture_portal_cleanup_plane_fds(gsr_capture_portal *self) {
for(int i = 0; i < self->num_dmabuf_data; ++i) {
if(self->dmabuf_data[i].fd > 0) {
close(self->dmabuf_data[i].fd);
self->dmabuf_data[i].fd = 0;
for(int i = 0; i < self->pipewire_data.num_dmabuf_data; ++i) {
if(self->pipewire_data.dmabuf_data[i].fd > 0) {
close(self->pipewire_data.dmabuf_data[i].fd);
self->pipewire_data.dmabuf_data[i].fd = 0;
}
}
self->num_dmabuf_data = 0;
self->pipewire_data.num_dmabuf_data = 0;
}
static void gsr_capture_portal_stop(gsr_capture_portal *self) {
@@ -238,9 +232,9 @@ static bool gsr_capture_portal_get_frame_dimensions(gsr_capture_portal *self) {
const double start_time = clock_get_monotonic_seconds();
while(clock_get_monotonic_seconds() - start_time < 5.0) {
if(gsr_pipewire_video_map_texture(&self->pipewire, self->texture_map, &self->region, &self->cursor_region, self->dmabuf_data, &self->num_dmabuf_data, &self->pipewire_fourcc, &self->pipewire_modifiers, &self->using_external_image)) {
self->capture_size.x = self->region.width;
self->capture_size.y = self->region.height;
if(gsr_pipewire_video_map_texture(&self->pipewire, self->texture_map, &self->pipewire_data)) {
self->capture_size.x = self->pipewire_data.region.width;
self->capture_size.y = self->pipewire_data.region.height;
fprintf(stderr, "gsr info: gsr_capture_portal_start: pipewire negotiation finished\n");
return true;
}
@@ -347,11 +341,11 @@ static int gsr_capture_portal_capture(gsr_capture *cap, gsr_capture_metadata *ca
}
/* TODO: Handle formats other than RGB(A) */
if(self->num_dmabuf_data == 0) {
if(gsr_pipewire_video_map_texture(&self->pipewire, self->texture_map, &self->region, &self->cursor_region, self->dmabuf_data, &self->num_dmabuf_data, &self->pipewire_fourcc, &self->pipewire_modifiers, &self->using_external_image)) {
if(self->region.width != self->capture_size.x || self->region.height != self->capture_size.y) {
self->capture_size.x = self->region.width;
self->capture_size.y = self->region.height;
if(self->pipewire_data.num_dmabuf_data == 0) {
if(gsr_pipewire_video_map_texture(&self->pipewire, self->texture_map, &self->pipewire_data)) {
if(self->pipewire_data.region.width != self->capture_size.x || self->pipewire_data.region.height != self->capture_size.y) {
self->capture_size.x = self->pipewire_data.region.width;
self->capture_size.y = self->pipewire_data.region.height;
gsr_color_conversion_clear(color_conversion);
}
} else {
@@ -370,32 +364,35 @@ static int gsr_capture_portal_capture(gsr_capture *cap, gsr_capture_metadata *ca
// TODO: Handle region crop
const bool fourcc_alpha = fourcc_has_alpha(self->pipewire_fourcc);
const bool fourcc_alpha = fourcc_has_alpha(self->pipewire_data.fourcc);
if(fourcc_alpha)
gsr_color_conversion_clear(color_conversion);
gsr_color_conversion_draw(color_conversion, self->using_external_image ? self->texture_map.external_texture_id : self->texture_map.texture_id,
gsr_color_conversion_draw(color_conversion, self->pipewire_data.using_external_image ? self->texture_map.external_texture_id : self->texture_map.texture_id,
target_pos, output_size,
(vec2i){self->region.x, self->region.y}, self->capture_size, self->capture_size,
GSR_ROT_0, GSR_SOURCE_COLOR_RGB, self->using_external_image, fourcc_alpha);
(vec2i){self->pipewire_data.region.x, self->pipewire_data.region.y}, self->capture_size, self->capture_size,
gsr_monitor_rotation_to_rotation(self->pipewire_data.rotation), GSR_SOURCE_COLOR_RGB, self->pipewire_data.using_external_image, fourcc_alpha);
if(self->params.record_cursor && self->texture_map.cursor_texture_id > 0 && self->cursor_region.width > 0) {
if(self->params.record_cursor && self->texture_map.cursor_texture_id > 0 && self->pipewire_data.cursor_region.width > 0) {
const vec2d scale = {
self->capture_size.x == 0 ? 0 : (double)output_size.x / (double)self->capture_size.x,
self->capture_size.y == 0 ? 0 : (double)output_size.y / (double)self->capture_size.y
};
const vec2i cursor_pos = {
target_pos.x + (self->cursor_region.x * scale.x),
target_pos.y + (self->cursor_region.y * scale.y)
target_pos.x + (self->pipewire_data.cursor_region.x * scale.x),
target_pos.y + (self->pipewire_data.cursor_region.y * scale.y)
};
self->params.egl->glEnable(GL_SCISSOR_TEST);
self->params.egl->glScissor(target_pos.x, target_pos.y, output_size.x, output_size.y);
gsr_color_conversion_draw(color_conversion, self->texture_map.cursor_texture_id,
(vec2i){cursor_pos.x, cursor_pos.y}, (vec2i){self->cursor_region.width * scale.x, self->cursor_region.height * scale.y},
(vec2i){0, 0}, (vec2i){self->cursor_region.width, self->cursor_region.height}, (vec2i){self->cursor_region.width, self->cursor_region.height},
GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false, true);
(vec2i){cursor_pos.x, cursor_pos.y},
(vec2i){self->pipewire_data.cursor_region.width * scale.x, self->pipewire_data.cursor_region.height * scale.y},
(vec2i){0, 0},
(vec2i){self->pipewire_data.cursor_region.width, self->pipewire_data.cursor_region.height},
(vec2i){self->pipewire_data.cursor_region.width, self->pipewire_data.cursor_region.height},
gsr_monitor_rotation_to_rotation(self->pipewire_data.rotation), GSR_SOURCE_COLOR_RGB, false, true);
self->params.egl->glDisable(GL_SCISSOR_TEST);
}

View File

@@ -21,6 +21,29 @@
#define SPA_POD_PROP_FLAG_DONT_FIXATE (1 << 4)
#endif
#if !PW_CHECK_VERSION(0, 3, 62)
enum spa_meta_videotransform_value {
SPA_META_TRANSFORMATION_None = 0, /**< no transform */
SPA_META_TRANSFORMATION_90, /**< 90 degree counter-clockwise */
SPA_META_TRANSFORMATION_180, /**< 180 degree counter-clockwise */
SPA_META_TRANSFORMATION_270, /**< 270 degree counter-clockwise */
SPA_META_TRANSFORMATION_Flipped, /**< 180 degree flipped around the vertical axis. Equivalent
* to a reflexion through the vertical line splitting the
* buffer in two equal sized parts */
SPA_META_TRANSFORMATION_Flipped90, /**< flip then rotate around 90 degree counter-clockwise */
SPA_META_TRANSFORMATION_Flipped180, /**< flip then rotate around 180 degree counter-clockwise */
SPA_META_TRANSFORMATION_Flipped270, /**< flip then rotate around 270 degree counter-clockwise */
};
/** a transformation of the buffer */
struct spa_meta_videotransform {
uint32_t transform; /**< orientation transformation that was applied to the buffer,
* one of enum spa_meta_videotransform_value */
};
#define SPA_META_VideoTransform 8
#endif
#define CURSOR_META_SIZE(width, height) \
(sizeof(struct spa_meta_cursor) + sizeof(struct spa_meta_bitmap) + \
width * height * 4)
@@ -157,6 +180,27 @@ static void on_process_cb(void *user_data) {
self->crop.valid = false;
}
struct spa_meta_videotransform *video_transform = spa_buffer_find_meta_data(buffer, SPA_META_VideoTransform, sizeof(*video_transform));
enum spa_meta_videotransform_value transform = SPA_META_TRANSFORMATION_None;
if(video_transform)
transform = video_transform->transform;
self->rotation = GSR_MONITOR_ROT_0;
switch(transform) {
case SPA_META_TRANSFORMATION_90:
self->rotation = GSR_MONITOR_ROT_90;
break;
case SPA_META_TRANSFORMATION_180:
self->rotation = GSR_MONITOR_ROT_180;
break;
case SPA_META_TRANSFORMATION_270:
self->rotation = GSR_MONITOR_ROT_270;
break;
default:
// TODO: Support other rotations. Wayland compositors dont use them yet so it's ok to not support it now
break;
}
pthread_mutex_unlock(&self->mutex);
read_metadata:
@@ -246,17 +290,18 @@ static void on_param_changed_cb(void *user_data, uint32_t id, const struct spa_p
fprintf(stderr, "gsr info: pipewire: Size: %dx%d\n", self->format.info.raw.size.width, self->format.info.raw.size.height);
fprintf(stderr, "gsr info: pipewire: Framerate: %d/%d\n", self->format.info.raw.framerate.num, self->format.info.raw.framerate.denom);
uint8_t params_buffer[1024];
uint8_t params_buffer[2048];
struct spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer));
const struct spa_pod *params[4];
const struct spa_pod *params[5];
int param_index = 0;
params[0] = spa_pod_builder_add_object(
params[param_index++] = spa_pod_builder_add_object(
&pod_builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoCrop),
SPA_PARAM_META_size,
SPA_POD_Int(sizeof(struct spa_meta_region)));
params[1] = spa_pod_builder_add_object(
params[param_index++] = spa_pod_builder_add_object(
&pod_builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoDamage),
SPA_PARAM_META_size, SPA_POD_CHOICE_RANGE_Int(
@@ -264,7 +309,7 @@ static void on_param_changed_cb(void *user_data, uint32_t id, const struct spa_p
sizeof(struct spa_meta_region) * 1,
sizeof(struct spa_meta_region) * 16));
params[2] = spa_pod_builder_add_object(
params[param_index++] = spa_pod_builder_add_object(
&pod_builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Cursor),
SPA_PARAM_META_size,
@@ -272,11 +317,22 @@ static void on_param_changed_cb(void *user_data, uint32_t id, const struct spa_p
CURSOR_META_SIZE(1, 1),
CURSOR_META_SIZE(1024, 1024)));
params[3] = spa_pod_builder_add_object(
params[param_index++] = spa_pod_builder_add_object(
&pod_builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
SPA_PARAM_BUFFERS_dataType, SPA_POD_Int(buffer_types));
pw_stream_update_params(self->stream, params, 4);
#if PW_CHECK_VERSION(0, 3, 62)
if (check_pw_version(&self->server_version, 0, 3, 62)) {
/* Video transformation */
params[param_index++] = spa_pod_builder_add_object(&pod_builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
SPA_PARAM_META_type,
SPA_POD_Id(SPA_META_VideoTransform),
SPA_PARAM_META_size,
SPA_POD_Int(sizeof(struct spa_meta_videotransform)));
}
#endif
pw_stream_update_params(self->stream, params, param_index);
self->negotiated = true;
}
@@ -790,14 +846,15 @@ static void gsr_pipewire_video_update_cursor_texture(gsr_pipewire_video *self, g
self->cursor.data = NULL;
}
bool gsr_pipewire_video_map_texture(gsr_pipewire_video *self, gsr_texture_map texture_map, gsr_pipewire_video_region *region, gsr_pipewire_video_region *cursor_region, gsr_pipewire_video_dmabuf_data *dmabuf_data, int *num_dmabuf_data, uint32_t *fourcc, uint64_t *modifiers, bool *using_external_image) {
bool gsr_pipewire_video_map_texture(gsr_pipewire_video *self, gsr_texture_map texture_map, gsr_map_texture_output *output) {
for(int i = 0; i < GSR_PIPEWIRE_VIDEO_DMABUF_MAX_PLANES; ++i) {
memset(&dmabuf_data[i], 0, sizeof(gsr_pipewire_video_dmabuf_data));
memset(&output->dmabuf_data[i], 0, sizeof(gsr_pipewire_video_dmabuf_data));
}
*num_dmabuf_data = 0;
*using_external_image = self->external_texture_fallback;
*fourcc = 0;
*modifiers = 0;
output->num_dmabuf_data = 0;
output->using_external_image = self->external_texture_fallback;
output->fourcc = 0;
output->modifiers = 0;
output->rotation = GSR_MONITOR_ROT_0;
pthread_mutex_lock(&self->mutex);
if(!self->negotiated || self->dmabuf_data[0].fd <= 0) {
@@ -812,39 +869,47 @@ bool gsr_pipewire_video_map_texture(gsr_pipewire_video *self, gsr_texture_map te
}
gsr_pipewire_video_bind_image_to_texture_with_fallback(self, texture_map, image);
*using_external_image = self->external_texture_fallback;
output->using_external_image = self->external_texture_fallback;
self->egl->eglDestroyImage(self->egl->egl_display, image);
gsr_pipewire_video_update_cursor_texture(self, texture_map);
region->x = 0;
region->y = 0;
output->region.x = 0;
output->region.y = 0;
region->width = self->format.info.raw.size.width;
region->height = self->format.info.raw.size.height;
output->region.width = self->format.info.raw.size.width;
output->region.height = self->format.info.raw.size.height;
if(self->crop.valid) {
region->x = self->crop.x;
region->y = self->crop.y;
output->region.x = self->crop.x;
output->region.y = self->crop.y;
region->width = self->crop.width;
region->height = self->crop.height;
output->region.width = self->crop.width;
output->region.height = self->crop.height;
}
// TODO: Test transform + cropping
if(self->rotation == GSR_MONITOR_ROT_90 || self->rotation == GSR_MONITOR_ROT_270) {
const int temp = output->region.width;
output->region.width = output->region.height;
output->region.height = temp;
}
/* TODO: Test if cursor hotspot is correct */
cursor_region->x = self->cursor.x - self->cursor.hotspot_x;
cursor_region->y = self->cursor.y - self->cursor.hotspot_y;
output->cursor_region.x = self->cursor.x - self->cursor.hotspot_x;
output->cursor_region.y = self->cursor.y - self->cursor.hotspot_y;
cursor_region->width = self->cursor.width;
cursor_region->height = self->cursor.height;
output->cursor_region.width = self->cursor.width;
output->cursor_region.height = self->cursor.height;
for(size_t i = 0; i < self->dmabuf_num_planes; ++i) {
dmabuf_data[i] = self->dmabuf_data[i];
output->dmabuf_data[i] = self->dmabuf_data[i];
self->dmabuf_data[i].fd = -1;
}
*num_dmabuf_data = self->dmabuf_num_planes;
*fourcc = spa_video_format_to_drm_format(self->format.info.raw.format);
*modifiers = self->format.info.raw.modifier;
output->num_dmabuf_data = self->dmabuf_num_planes;
output->fourcc = spa_video_format_to_drm_format(self->format.info.raw.format);
output->modifiers = self->format.info.raw.modifier;
output->rotation = self->rotation;
self->dmabuf_num_planes = 0;
pthread_mutex_unlock(&self->mutex);